Merge pull request #424 from Filip Hrisafov

* gh-424:
  Polish "Add support for configuring default request and response preprocessors"
  Add support for configuring default request and response preprocessors
This commit is contained in:
Andy Wilkinson
2017-10-27 12:10:16 +01:00
19 changed files with 730 additions and 62 deletions

View File

@@ -114,3 +114,27 @@ include::{examples-dir}/com/example/mockmvc/CustomDefaultSnippets.java[tags=cust
----
include::{examples-dir}/com/example/restassured/CustomDefaultSnippets.java[tags=custom-default-snippets]
----
[[configuration-default-preprocessors]]
=== Default operation preprocessors
You can configure default request and response preprocessors during setup using the
`RestDocumentationConfigurer` API. For example, to remove the `Foo` headers from all requests
and pretty print all responses:
[source,java,indent=0,role="primary"]
.MockMvc
----
include::{examples-dir}/com/example/mockmvc/CustomDefaultOperationPreprocessors.java[tags=custom-default-operation-preprocessors]
----
<1> Apply a request preprocessor that will remove the header named `Foo`.
<2> Apply a response preprocessor that will pretty print its content.
[source,java,indent=0,role="secondary"]
.REST Assured
----
include::{examples-dir}/com/example/restassured/CustomDefaultOperationPreprocessors.java[tags=custom-default-operation-preprocessors]
----
<1> Apply a request preprocessor that will remove the header named `Foo`.
<2> Apply a response preprocessor that will pretty print its content.

View File

@@ -27,29 +27,25 @@ include::{examples-dir}/com/example/restassured/PerTestPreprocessing.java[tags=p
<2> Apply a response preprocessor that will pretty print its content.
Alternatively, you may want to apply the same preprocessors to every test. You can do
so by configuring the preprocessors in your `@Before` method and using the
<<documentating-your-api-parameterized-output-directories, support for parameterized
output directories>>:
so by configuring the preprocessors using the `RestDocumentationConfigurer` API in your
`@Before` method. For example to remove the `Foo` header from all requests and pretty print
all responses:
[source,java,indent=0,role="primary"]
.MockMvc
----
include::{examples-dir}/com/example/mockmvc/EveryTestPreprocessing.java[tags=setup]
----
<1> Create a `RestDocumentationResultHandler`, configured to preprocess the request
and response.
<2> Create a `MockMvc` instance, configured to always call the documentation result
handler.
<1> Apply a request preprocessor that will remove the header named `Foo`.
<2> Apply a response preprocessor that will pretty print its content.
[source,java,indent=0,role="secondary"]
.REST Assured
----
include::{examples-dir}/com/example/restassured/EveryTestPreprocessing.java[tags=setup]
----
<1> Create a `RestDocumentationFilter`, configured to preprocess the request
and response.
<2> Create a `RequestSpecification` instance, configured to always call the documentation
filter.
<1> Apply a request preprocessor that will remove the header named `Foo`.
<2> Apply a response preprocessor that will pretty print its content.
Then, in each test, any configuration specific to that test can be performed. For example:
@@ -58,17 +54,12 @@ Then, in each test, any configuration specific to that test can be performed. Fo
----
include::{examples-dir}/com/example/mockmvc/EveryTestPreprocessing.java[tags=use]
----
<1> The request and response will be preprocessed due to the use of `alwaysDo` above.
<2> Document the links specific to the resource that is being tested
[source,java,indent=0,role="secondary"]
.REST Assured
----
include::{examples-dir}/com/example/restassured/EveryTestPreprocessing.java[tags=use]
----
<1> The request and response will be preprocessed due to the configuration of the
`RequestSpecification` in the `setUp` method.
<2> Document the links specific to the resource that is being tested
Various built in preprocessors, including those illustrated above, are available via the
static methods on `Preprocessors`. See <<Preprocessors, below>> for further details.

View File

@@ -0,0 +1,53 @@
/*
* 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
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.example.mockmvc;
import org.junit.Before;
import org.junit.Rule;
import org.springframework.restdocs.JUnitRestDocumentation;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;
import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.documentationConfiguration;
import static org.springframework.restdocs.operation.preprocess.Preprocessors.prettyPrint;
import static org.springframework.restdocs.operation.preprocess.Preprocessors.removeHeaders;
public class CustomDefaultOperationPreprocessors {
@Rule
public final JUnitRestDocumentation restDocumentation = new JUnitRestDocumentation();
private WebApplicationContext context;
@SuppressWarnings("unused")
private MockMvc mockMvc;
@Before
public void setup() {
// tag::custom-default-operation-preprocessors[]
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.context)
.apply(documentationConfiguration(this.restDocumentation)
.operationPreprocessors()
.withRequestDefaults(removeHeaders("Foo")) // <1>
.withResponseDefaults(prettyPrint())) // <2>
.build();
// end::custom-default-operation-preprocessors[]
}
}

View File

@@ -19,7 +19,6 @@ package com.example.mockmvc;
import org.junit.Before;
import org.junit.Rule;
import org.springframework.restdocs.JUnitRestDocumentation;
import org.springframework.restdocs.mockmvc.RestDocumentationResultHandler;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;
@@ -29,8 +28,6 @@ import static org.springframework.restdocs.hypermedia.HypermediaDocumentation.li
import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document;
import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.documentationConfiguration;
import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.get;
import static org.springframework.restdocs.operation.preprocess.Preprocessors.preprocessRequest;
import static org.springframework.restdocs.operation.preprocess.Preprocessors.preprocessResponse;
import static org.springframework.restdocs.operation.preprocess.Preprocessors.prettyPrint;
import static org.springframework.restdocs.operation.preprocess.Preprocessors.removeHeaders;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@@ -45,26 +42,21 @@ public class EveryTestPreprocessing {
// tag::setup[]
private MockMvc mockMvc;
private RestDocumentationResultHandler documentationHandler;
@Before
public void setup() {
this.documentationHandler = document("{method-name}", // <1>
preprocessRequest(removeHeaders("Foo")),
preprocessResponse(prettyPrint()));
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.context)
.apply(documentationConfiguration(this.restDocumentation))
.alwaysDo(this.documentationHandler) // <2>
.build();
.apply(documentationConfiguration(this.restDocumentation).operationPreprocessors()
.withRequestDefaults(removeHeaders("Foo")) // <1>
.withResponseDefaults(prettyPrint())) // <2>
.build();
}
// end::setup[]
public void use() throws Exception {
// tag::use[]
this.mockMvc.perform(get("/")) // <1>
this.mockMvc.perform(get("/"))
.andExpect(status().isOk())
.andDo(this.documentationHandler.document( // <2>
.andDo(document("index",
links(linkWithRel("self").description("Canonical self link"))
));
// end::use[]

View File

@@ -0,0 +1,49 @@
/*
* 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
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.example.restassured;
import io.restassured.builder.RequestSpecBuilder;
import io.restassured.specification.RequestSpecification;
import org.junit.Before;
import org.junit.Rule;
import org.springframework.restdocs.JUnitRestDocumentation;
import static org.springframework.restdocs.operation.preprocess.Preprocessors.prettyPrint;
import static org.springframework.restdocs.operation.preprocess.Preprocessors.removeHeaders;
import static org.springframework.restdocs.restassured3.RestAssuredRestDocumentation.documentationConfiguration;
public class CustomDefaultOperationPreprocessors {
@Rule
public final JUnitRestDocumentation restDocumentation = new JUnitRestDocumentation();
@SuppressWarnings("unused")
private RequestSpecification spec;
@Before
public void setup() {
// tag::custom-default-operation-preprocessors[]
this.spec = new RequestSpecBuilder()
.addFilter(documentationConfiguration(this.restDocumentation).operationPreprocessors()
.withRequestDefaults(removeHeaders("Foo")) // <1>
.withResponseDefaults(prettyPrint())) // <2>
.build();
// end::custom-default-operation-preprocessors[]
}
}

View File

@@ -23,13 +23,10 @@ import org.junit.Before;
import org.junit.Rule;
import org.springframework.restdocs.JUnitRestDocumentation;
import org.springframework.restdocs.restassured3.RestDocumentationFilter;
import static org.hamcrest.CoreMatchers.is;
import static org.springframework.restdocs.hypermedia.HypermediaDocumentation.linkWithRel;
import static org.springframework.restdocs.hypermedia.HypermediaDocumentation.links;
import static org.springframework.restdocs.operation.preprocess.Preprocessors.preprocessRequest;
import static org.springframework.restdocs.operation.preprocess.Preprocessors.preprocessResponse;
import static org.springframework.restdocs.operation.preprocess.Preprocessors.prettyPrint;
import static org.springframework.restdocs.operation.preprocess.Preprocessors.removeHeaders;
import static org.springframework.restdocs.restassured3.RestAssuredRestDocumentation.document;
@@ -43,26 +40,21 @@ public class EveryTestPreprocessing {
// tag::setup[]
private RequestSpecification spec;
private RestDocumentationFilter documentationFilter;
@Before
public void setup() {
this.documentationFilter = document("{method-name}",
preprocessRequest(removeHeaders("Foo")),
preprocessResponse(prettyPrint())); // <1>
this.spec = new RequestSpecBuilder()
.addFilter(documentationConfiguration(this.restDocumentation))
.addFilter(this.documentationFilter)// <2>
.build();
.addFilter(documentationConfiguration(this.restDocumentation).operationPreprocessors()
.withRequestDefaults(removeHeaders("Foo")) // <1>
.withResponseDefaults(prettyPrint())) // <2>
.build();
}
// end::setup[]
public void use() throws Exception {
// tag::use[]
RestAssured.given(this.spec) // <1>
.filter(this.documentationFilter.document( // <2>
links(linkWithRel("self").description("Canonical self link"))))
RestAssured.given(this.spec)
.filter(document("index",
links(linkWithRel("self").description("Canonical self link"))))
.when().get("/")
.then().assertThat().statusCode(is(200));
// end::use[]

View File

@@ -0,0 +1,91 @@
/*
* 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
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.restdocs.config;
import java.util.Map;
import org.springframework.restdocs.RestDocumentationContext;
import org.springframework.restdocs.generate.RestDocumentationGenerator;
import org.springframework.restdocs.operation.preprocess.OperationPreprocessor;
import org.springframework.restdocs.operation.preprocess.OperationRequestPreprocessor;
import org.springframework.restdocs.operation.preprocess.OperationResponsePreprocessor;
import org.springframework.restdocs.operation.preprocess.Preprocessors;
/**
* A configurer that can be used to configure the default operation preprocessors.
*
* @param <PARENT> The type of the configurer's parent
* @param <TYPE> The concrete type of the configurer to be returned from chained methods
* @author Filip Hrisafov
* @author Andy Wilkinson
* @since 2.0.0
*/
public abstract class OperationPreprocessorsConfigurer<PARENT, TYPE>
extends AbstractNestedConfigurer<PARENT> {
private OperationRequestPreprocessor defaultOperationRequestPreprocessor;
private OperationResponsePreprocessor defaultOperationResponsePreprocessor;
/**
* Creates a new {@code OperationPreprocessorConfigurer} with the given
* {@code parent}.
*
* @param parent the parent
*/
protected OperationPreprocessorsConfigurer(PARENT parent) {
super(parent);
}
@Override
public void apply(Map<String, Object> configuration,
RestDocumentationContext context) {
configuration.put(
RestDocumentationGenerator.ATTRIBUTE_NAME_DEFAULT_OPERATION_REQUEST_PREPROCESSOR,
this.defaultOperationRequestPreprocessor);
configuration.put(
RestDocumentationGenerator.ATTRIBUTE_NAME_DEFAULT_OPERATION_RESPONSE_PREPROCESSOR,
this.defaultOperationResponsePreprocessor);
}
/**
* Configures the default operation request preprocessors.
*
* @param preprocessors the preprocessors
* @return {@code this}
*/
@SuppressWarnings("unchecked")
public TYPE withRequestDefaults(OperationPreprocessor... preprocessors) {
this.defaultOperationRequestPreprocessor = Preprocessors
.preprocessRequest(preprocessors);
return (TYPE) this;
}
/**
* Configures the default operation response preprocessors.
*
* @param preprocessors the preprocessors
* @return {@code this}
*/
@SuppressWarnings("unchecked")
public TYPE withResponseDefaults(OperationPreprocessor... preprocessors) {
this.defaultOperationResponsePreprocessor = Preprocessors
.preprocessResponse(preprocessors);
return (TYPE) this;
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2016 the original author or authors.
* 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.
@@ -35,13 +35,15 @@ import org.springframework.restdocs.templates.mustache.MustacheTemplateEngine;
/**
* Abstract base class for the configuration of Spring REST Docs.
*
* @param <S> The concrete type of the {@link SnippetConfigurer}.
* @param <S> The concrete type of the {@link SnippetConfigurer}
* @param <P> The concrete type of the {@link OperationPreprocessorsConfigurer}
* @param <T> The concrete type of this configurer, to be returned from methods that
* support chaining
* @author Andy Wilkinson
* @author Filip Hrisafov
* @since 1.1.0
*/
public abstract class RestDocumentationConfigurer<S extends AbstractConfigurer, T> {
public abstract class RestDocumentationConfigurer<S extends AbstractConfigurer, P extends AbstractConfigurer, T> {
private final WriterResolverConfigurer writerResolverConfigurer = new WriterResolverConfigurer();
@@ -55,6 +57,14 @@ public abstract class RestDocumentationConfigurer<S extends AbstractConfigurer,
*/
public abstract S snippets();
/**
* Returns an {@link OperationPreprocessorsConfigurer} that can be used to configure
* the operation request and response preprocessors that will be used.
*
* @return the operation preprocessors configurer
*/
public abstract P operationPreprocessors();
/**
* Configures the {@link TemplateEngine} that will be used for snippet rendering.
*
@@ -90,7 +100,8 @@ public abstract class RestDocumentationConfigurer<S extends AbstractConfigurer,
protected final void apply(Map<String, Object> configuration,
RestDocumentationContext context) {
List<AbstractConfigurer> configurers = Arrays.asList(snippets(),
this.templateEngineConfigurer, this.writerResolverConfigurer);
operationPreprocessors(), this.templateEngineConfigurer,
this.writerResolverConfigurer);
for (AbstractConfigurer configurer : configurers) {
configurer.apply(configuration, context);
}

View File

@@ -41,6 +41,7 @@ import org.springframework.util.Assert;
* @param <REQ> the request type that can be handled
* @param <RESP> the response type that can be handled
* @author Andy Wilkinson
* @author Filip Hrisafov
* @since 1.1.0
*/
public final class RestDocumentationGenerator<REQ, RESP> {
@@ -55,6 +56,18 @@ public final class RestDocumentationGenerator<REQ, RESP> {
*/
public static final String ATTRIBUTE_NAME_DEFAULT_SNIPPETS = "org.springframework.restdocs.defaultSnippets";
/**
* Name of the operation attribute used to hold the default operation request
* preprocessor.
*/
public static final String ATTRIBUTE_NAME_DEFAULT_OPERATION_REQUEST_PREPROCESSOR = "org.springframework.restdocs.defaultOperationRequestPreprocessor";
/**
* Name of the operation attribute used to hold the default operation response
* preprocessor.
*/
public static final String ATTRIBUTE_NAME_DEFAULT_OPERATION_RESPONSE_PREPROCESSOR = "org.springframework.restdocs.defaultOperationResponsePreprocessor";
private final String identifier;
private final OperationRequestPreprocessor requestPreprocessor;
@@ -183,12 +196,11 @@ public final class RestDocumentationGenerator<REQ, RESP> {
* @throws RestDocumentationGenerationException if a failure occurs during handling
*/
public void handle(REQ request, RESP response, Map<String, Object> configuration) {
OperationRequest operationRequest = this.requestPreprocessor
.preprocess(this.requestConverter.convert(request));
OperationResponse operationResponse = this.responsePreprocessor
.preprocess(this.responseConverter.convert(response));
Map<String, Object> attributes = new HashMap<>(configuration);
OperationRequest operationRequest = preprocessRequest(
this.requestConverter.convert(request), attributes);
OperationResponse operationResponse = preprocessResponse(
this.responseConverter.convert(response), attributes);
Operation operation = new StandardOperation(this.identifier, operationRequest,
operationResponse, attributes);
try {
@@ -228,6 +240,51 @@ public final class RestDocumentationGenerator<REQ, RESP> {
return combinedSnippets;
}
private OperationRequest preprocessRequest(OperationRequest request,
Map<String, Object> configuration) {
List<OperationRequestPreprocessor> requestPreprocessors = getRequestPreprocessors(
configuration);
for (OperationRequestPreprocessor preprocessor : requestPreprocessors) {
request = preprocessor.preprocess(request);
}
return request;
}
private List<OperationRequestPreprocessor> getRequestPreprocessors(
Map<String, Object> configuration) {
return getPreprocessors(this.requestPreprocessor,
RestDocumentationGenerator.ATTRIBUTE_NAME_DEFAULT_OPERATION_REQUEST_PREPROCESSOR,
configuration);
}
private OperationResponse preprocessResponse(OperationResponse response,
Map<String, Object> configuration) {
for (OperationResponsePreprocessor preprocessor : getResponsePreprocessors(
configuration)) {
response = preprocessor.preprocess(response);
}
return response;
}
private List<OperationResponsePreprocessor> getResponsePreprocessors(
Map<String, Object> configuration) {
return getPreprocessors(this.responsePreprocessor,
RestDocumentationGenerator.ATTRIBUTE_NAME_DEFAULT_OPERATION_RESPONSE_PREPROCESSOR,
configuration);
}
@SuppressWarnings("unchecked")
private <T> List<T> getPreprocessors(T preprocessor, String preprocessorAttribute,
Map<String, Object> configuration) {
List<T> preprocessors = new ArrayList<>(2);
preprocessors.add(preprocessor);
T defaultResponsePreprocessor = (T) configuration.get(preprocessorAttribute);
if (defaultResponsePreprocessor != null) {
preprocessors.add(defaultResponsePreprocessor);
}
return preprocessors;
}
private static final class IdentityOperationRequestPreprocessor
implements OperationRequestPreprocessor {

View File

@@ -35,8 +35,10 @@ import org.springframework.restdocs.operation.OperationResponse;
import org.springframework.restdocs.operation.OperationResponseFactory;
import org.springframework.restdocs.operation.RequestConverter;
import org.springframework.restdocs.operation.ResponseConverter;
import org.springframework.restdocs.operation.preprocess.OperationPreprocessor;
import org.springframework.restdocs.operation.preprocess.OperationRequestPreprocessor;
import org.springframework.restdocs.operation.preprocess.OperationResponsePreprocessor;
import org.springframework.restdocs.operation.preprocess.Preprocessors;
import org.springframework.restdocs.snippet.Snippet;
import static org.hamcrest.CoreMatchers.equalTo;
@@ -51,6 +53,7 @@ import static org.mockito.Mockito.verifyNoMoreInteractions;
* Tests for {@link RestDocumentationGenerator}.
*
* @author Andy Wilkinson
* @author Filip Hrisafov
*/
public class RestDocumentationGeneratorTests {
@@ -75,6 +78,12 @@ public class RestDocumentationGeneratorTests {
private final Snippet snippet = mock(Snippet.class);
private final OperationPreprocessor requestPreprocessor = mock(
OperationPreprocessor.class);
private final OperationPreprocessor responsePreprocessor = mock(
OperationPreprocessor.class);
@Test
public void basicHandling() throws IOException {
given(this.requestConverter.convert(this.request))
@@ -107,6 +116,63 @@ public class RestDocumentationGeneratorTests {
verifySnippetInvocation(defaultSnippet2, configuration);
}
@Test
public void defaultOperationRequestPreprocessorsAreCalled() throws IOException {
given(this.requestConverter.convert(this.request))
.willReturn(this.operationRequest);
given(this.responseConverter.convert(this.response))
.willReturn(this.operationResponse);
HashMap<String, Object> configuration = new HashMap<>();
OperationPreprocessor defaultPreprocessor1 = mock(OperationPreprocessor.class);
OperationPreprocessor defaultPreprocessor2 = mock(OperationPreprocessor.class);
configuration.put(
RestDocumentationGenerator.ATTRIBUTE_NAME_DEFAULT_OPERATION_REQUEST_PREPROCESSOR,
Preprocessors.preprocessRequest(defaultPreprocessor1,
defaultPreprocessor2));
OperationRequest first = createRequest();
OperationRequest second = createRequest();
OperationRequest third = createRequest();
given(this.requestPreprocessor.preprocess(this.operationRequest))
.willReturn(first);
given(defaultPreprocessor1.preprocess(first)).willReturn(second);
given(defaultPreprocessor2.preprocess(second)).willReturn(third);
new RestDocumentationGenerator<>("id", this.requestConverter,
this.responseConverter,
Preprocessors.preprocessRequest(this.requestPreprocessor), this.snippet)
.handle(this.request, this.response, configuration);
verifySnippetInvocation(this.snippet, third, this.operationResponse,
configuration, 1);
}
@Test
public void defaultOperationResponsePreprocessorsAreCalled() throws IOException {
given(this.requestConverter.convert(this.request))
.willReturn(this.operationRequest);
given(this.responseConverter.convert(this.response))
.willReturn(this.operationResponse);
HashMap<String, Object> configuration = new HashMap<>();
OperationPreprocessor defaultPreprocessor1 = mock(OperationPreprocessor.class);
OperationPreprocessor defaultPreprocessor2 = mock(OperationPreprocessor.class);
configuration.put(
RestDocumentationGenerator.ATTRIBUTE_NAME_DEFAULT_OPERATION_RESPONSE_PREPROCESSOR,
Preprocessors.preprocessResponse(defaultPreprocessor1,
defaultPreprocessor2));
OperationResponse first = createResponse();
OperationResponse second = createResponse();
OperationResponse third = new OperationResponseFactory()
.createFrom(this.operationResponse, new HttpHeaders());
given(this.responsePreprocessor.preprocess(this.operationResponse))
.willReturn(first);
given(defaultPreprocessor1.preprocess(first)).willReturn(second);
given(defaultPreprocessor2.preprocess(second)).willReturn(third);
new RestDocumentationGenerator<>("id", this.requestConverter,
this.responseConverter,
Preprocessors.preprocessResponse(this.responsePreprocessor), this.snippet)
.handle(this.request, this.response, configuration);
verifySnippetInvocation(this.snippet, this.operationRequest, third, configuration,
1);
}
@Test
public void newGeneratorOnlyCallsItsSnippets() throws IOException {
OperationRequestPreprocessor requestPreprocessor = mock(
@@ -141,12 +207,27 @@ public class RestDocumentationGeneratorTests {
private void verifySnippetInvocation(Snippet snippet, Map<String, Object> attributes,
int times) throws IOException {
verifySnippetInvocation(snippet, this.operationRequest, this.operationResponse,
attributes, times);
}
private void verifySnippetInvocation(Snippet snippet,
OperationRequest operationRequest, OperationResponse operationResponse,
Map<String, Object> attributes, int times) throws IOException {
ArgumentCaptor<Operation> operation = ArgumentCaptor.forClass(Operation.class);
verify(snippet, Mockito.times(times)).document(operation.capture());
assertThat(this.operationRequest, is(equalTo(operation.getValue().getRequest())));
assertThat(this.operationResponse,
is(equalTo(operation.getValue().getResponse())));
assertThat(operationRequest, is(equalTo(operation.getValue().getRequest())));
assertThat(operationResponse, is(equalTo(operation.getValue().getResponse())));
assertThat(attributes, is(equalTo(operation.getValue().getAttributes())));
}
private static OperationRequest createRequest() {
return new OperationRequestFactory().create(URI.create("http://localhost:8080"),
null, null, new HttpHeaders(), null, null);
}
private static OperationResponse createResponse() {
return new OperationResponseFactory().create(null, null, null);
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2016 the original author or authors.
* 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.
@@ -16,6 +16,8 @@
package org.springframework.restdocs.config;
import java.net.URI;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -23,6 +25,9 @@ import java.util.Map;
import org.hamcrest.Matchers;
import org.junit.Test;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.restdocs.ManualRestDocumentation;
import org.springframework.restdocs.RestDocumentationContext;
import org.springframework.restdocs.cli.CliDocumentation;
@@ -31,6 +36,13 @@ import org.springframework.restdocs.cli.HttpieRequestSnippet;
import org.springframework.restdocs.generate.RestDocumentationGenerator;
import org.springframework.restdocs.http.HttpRequestSnippet;
import org.springframework.restdocs.http.HttpResponseSnippet;
import org.springframework.restdocs.operation.OperationRequest;
import org.springframework.restdocs.operation.OperationRequestFactory;
import org.springframework.restdocs.operation.OperationResponse;
import org.springframework.restdocs.operation.OperationResponseFactory;
import org.springframework.restdocs.operation.preprocess.OperationRequestPreprocessor;
import org.springframework.restdocs.operation.preprocess.OperationResponsePreprocessor;
import org.springframework.restdocs.operation.preprocess.Preprocessors;
import org.springframework.restdocs.payload.RequestBodySnippet;
import org.springframework.restdocs.payload.ResponseBodySnippet;
import org.springframework.restdocs.snippet.Snippet;
@@ -45,6 +57,7 @@ import org.springframework.test.util.ReflectionTestUtils;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.instanceOf;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.nullValue;
import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.hasEntry;
import static org.junit.Assert.assertThat;
@@ -54,6 +67,7 @@ import static org.mockito.Mockito.mock;
* Tests for {@link RestDocumentationConfigurer}.
*
* @author Andy Wilkinson
* @author Filip Hrisafov
*/
public class RestDocumentationConfigurerTests {
@@ -88,6 +102,14 @@ public class RestDocumentationConfigurerTests {
assertThat(snippetConfiguration.getEncoding(), is(equalTo("UTF-8")));
assertThat(snippetConfiguration.getTemplateFormat(),
is(equalTo(TemplateFormats.asciidoctor())));
OperationRequestPreprocessor defaultOperationRequestPreprocessor = (OperationRequestPreprocessor) configuration
.get(RestDocumentationGenerator.ATTRIBUTE_NAME_DEFAULT_OPERATION_REQUEST_PREPROCESSOR);
assertThat(defaultOperationRequestPreprocessor, is(nullValue()));
OperationResponsePreprocessor defaultOperationResponsePreprocessor = (OperationResponsePreprocessor) configuration
.get(RestDocumentationGenerator.ATTRIBUTE_NAME_DEFAULT_OPERATION_RESPONSE_PREPROCESSOR);
assertThat(defaultOperationResponsePreprocessor, is(nullValue()));
}
@Test
@@ -200,6 +222,41 @@ public class RestDocumentationConfigurerTests {
assertThat(templateContext.size(), equalTo(0));
}
@Test
public void customDefaultOperationRequestPreprocessor() {
Map<String, Object> configuration = new HashMap<>();
this.configurer.operationPreprocessors()
.withRequestDefaults(Preprocessors.prettyPrint(),
Preprocessors.removeHeaders("Foo"))
.apply(configuration, createContext());
OperationRequestPreprocessor preprocessor = (OperationRequestPreprocessor) configuration
.get(RestDocumentationGenerator.ATTRIBUTE_NAME_DEFAULT_OPERATION_REQUEST_PREPROCESSOR);
HttpHeaders headers = new HttpHeaders();
headers.add("Foo", "value");
OperationRequest request = new OperationRequestFactory().create(
URI.create("http://localhost:8080"), HttpMethod.GET, null, headers, null,
Collections.emptyList());
assertThat(preprocessor.preprocess(request).getHeaders().get("Foo"),
is(nullValue()));
}
@Test
public void customDefaultOperationResponsePreprocessor() {
Map<String, Object> configuration = new HashMap<>();
this.configurer.operationPreprocessors()
.withResponseDefaults(Preprocessors.prettyPrint(),
Preprocessors.removeHeaders("Foo"))
.apply(configuration, createContext());
OperationResponsePreprocessor preprocessor = (OperationResponsePreprocessor) configuration
.get(RestDocumentationGenerator.ATTRIBUTE_NAME_DEFAULT_OPERATION_RESPONSE_PREPROCESSOR);
HttpHeaders headers = new HttpHeaders();
headers.add("Foo", "value");
OperationResponse response = new OperationResponseFactory().create(HttpStatus.OK,
headers, null);
assertThat(preprocessor.preprocess(response).getHeaders().get("Foo"),
is(nullValue()));
}
private RestDocumentationContext createContext() {
ManualRestDocumentation manualRestDocumentation = new ManualRestDocumentation(
"build");
@@ -209,16 +266,23 @@ public class RestDocumentationConfigurerTests {
}
private static final class TestRestDocumentationConfigurer extends
RestDocumentationConfigurer<TestSnippetConfigurer, TestRestDocumentationConfigurer> {
RestDocumentationConfigurer<TestSnippetConfigurer, TestOperationPreprocessorsConfigurer, TestRestDocumentationConfigurer> {
private final TestSnippetConfigurer snippetConfigurer = new TestSnippetConfigurer(
this);
private final TestOperationPreprocessorsConfigurer operationPreprocessorsConfigurer = new TestOperationPreprocessorsConfigurer(
this);
@Override
public TestSnippetConfigurer snippets() {
return this.snippetConfigurer;
}
@Override
public TestOperationPreprocessorsConfigurer operationPreprocessors() {
return this.operationPreprocessorsConfigurer;
}
}
private static final class TestSnippetConfigurer extends
@@ -230,4 +294,13 @@ public class RestDocumentationConfigurerTests {
}
private static final class TestOperationPreprocessorsConfigurer extends
OperationPreprocessorsConfigurer<TestRestDocumentationConfigurer, TestOperationPreprocessorsConfigurer> {
protected TestOperationPreprocessorsConfigurer(
TestRestDocumentationConfigurer parent) {
super(parent);
}
}
}

View File

@@ -0,0 +1,50 @@
/*
* 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
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.restdocs.mockmvc;
import org.springframework.restdocs.config.OperationPreprocessorsConfigurer;
import org.springframework.test.web.servlet.request.RequestPostProcessor;
import org.springframework.test.web.servlet.setup.ConfigurableMockMvcBuilder;
import org.springframework.test.web.servlet.setup.MockMvcConfigurer;
import org.springframework.web.context.WebApplicationContext;
/**
* A configurer that can be used to configure the operation preprocessors.
*
* @author Filip Hrisafov
* @since 2.0.0
*/
public final class MockMvcOperationPreprocessorsConfigurer extends
OperationPreprocessorsConfigurer<MockMvcRestDocumentationConfigurer, MockMvcOperationPreprocessorsConfigurer>
implements MockMvcConfigurer {
MockMvcOperationPreprocessorsConfigurer(MockMvcRestDocumentationConfigurer parent) {
super(parent);
}
@Override
public void afterConfigurerAdded(ConfigurableMockMvcBuilder<?> builder) {
and().afterConfigurerAdded(builder);
}
@Override
public RequestPostProcessor beforeMockMvcCreated(
ConfigurableMockMvcBuilder<?> builder, WebApplicationContext context) {
return and().beforeMockMvcCreated(builder, context);
}
}

View File

@@ -33,10 +33,11 @@ import org.springframework.web.context.WebApplicationContext;
* A MockMvc-specific {@link RestDocumentationConfigurer}.
*
* @author Andy Wilkinson
* @author Filip Hrisafov
* @since 1.1.0
*/
public final class MockMvcRestDocumentationConfigurer extends
RestDocumentationConfigurer<MockMvcSnippetConfigurer, MockMvcRestDocumentationConfigurer>
RestDocumentationConfigurer<MockMvcSnippetConfigurer, MockMvcOperationPreprocessorsConfigurer, MockMvcRestDocumentationConfigurer>
implements MockMvcConfigurer {
private final MockMvcSnippetConfigurer snippetConfigurer = new MockMvcSnippetConfigurer(
@@ -44,6 +45,9 @@ public final class MockMvcRestDocumentationConfigurer extends
private final UriConfigurer uriConfigurer = new UriConfigurer(this);
private final MockMvcOperationPreprocessorsConfigurer operationPreprocessorsConfigurer = new MockMvcOperationPreprocessorsConfigurer(
this);
private final RestDocumentationContextProvider contextManager;
MockMvcRestDocumentationConfigurer(RestDocumentationContextProvider contextManager) {
@@ -76,6 +80,11 @@ public final class MockMvcRestDocumentationConfigurer extends
return this.snippetConfigurer;
}
@Override
public MockMvcOperationPreprocessorsConfigurer operationPreprocessors() {
return this.operationPreprocessorsConfigurer;
}
private final class ConfigurerApplyingRequestPostProcessor
implements RequestPostProcessor {

View File

@@ -103,6 +103,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
* @author Andy Wilkinson
* @author Dewet Diener
* @author Tomasz Kopczynski
* @author Filip Hrisafov
*/
@RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration
@@ -493,6 +494,42 @@ public class MockMvcRestDocumentationIntegrationTests {
.withContents(preprocessedRequest.content(prettyPrinted))));
}
@Test
public void defaultPreprocessedRequest() throws Exception {
Pattern pattern = Pattern.compile("(\"alpha\")");
MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.context)
.apply(documentationConfiguration(this.restDocumentation)
.operationPreprocessors()
.withRequestDefaults(prettyPrint(),
removeHeaders("a", HttpHeaders.HOST,
HttpHeaders.CONTENT_LENGTH),
replacePattern(pattern, "\"<<beta>>\"")))
.build();
MvcResult result = mockMvc
.perform(get("/").header("a", "alpha").header("b", "bravo")
.contentType(MediaType.APPLICATION_JSON)
.accept(MediaType.APPLICATION_JSON).content("{\"a\":\"alpha\"}"))
.andDo(document("default-preprocessed-request")).andReturn();
HttpRequestMatcher preprocessedRequest = httpRequest(asciidoctor(),
RequestMethod.GET, "/");
List<String> removedHeaders = Arrays.asList("a", HttpHeaders.HOST,
HttpHeaders.CONTENT_LENGTH);
for (String headerName : iterable(result.getRequest().getHeaderNames())) {
if (!removedHeaders.contains(headerName)) {
preprocessedRequest.header(headerName,
result.getRequest().getHeader(headerName));
}
}
String prettyPrinted = String.format("{%n \"a\" : \"<<beta>>\"%n}");
assertThat(
new File(
"build/generated-snippets/default-preprocessed-request/http-request.adoc"),
is(snippet(asciidoctor())
.withContents(preprocessedRequest.content(prettyPrinted))));
}
@Test
public void preprocessedResponse() throws Exception {
MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.context)
@@ -529,6 +566,33 @@ public class MockMvcRestDocumentationIntegrationTests {
.content(prettyPrinted))));
}
@Test
public void defaultPreprocessedResponse() throws Exception {
Pattern pattern = Pattern.compile("(\"alpha\")");
MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.context)
.apply(documentationConfiguration(this.restDocumentation)
.operationPreprocessors().withResponseDefaults(
prettyPrint(), maskLinks(), removeHeaders("a"),
replacePattern(pattern, "\"<<beta>>\"")))
.build();
mockMvc.perform(get("/").accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andDo(document("default-preprocessed-response"));
String prettyPrinted = String.format("{%n \"a\" : \"<<beta>>\",%n \"links\" : "
+ "[ {%n \"rel\" : \"rel\",%n \"href\" : \"...\"%n } ]%n}");
assertThat(
new File(
"build/generated-snippets/default-preprocessed-response/http-response.adoc"),
is(snippet(asciidoctor())
.withContents(httpResponse(asciidoctor(), HttpStatus.OK)
.header("Content-Type", "application/json;charset=UTF-8")
.header(HttpHeaders.CONTENT_LENGTH,
prettyPrinted.getBytes().length)
.content(prettyPrinted))));
}
@Test
public void customSnippetTemplate() throws Exception {
MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.context)

View File

@@ -0,0 +1,48 @@
/*
* 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
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.restdocs.restassured3;
import io.restassured.filter.Filter;
import io.restassured.filter.FilterContext;
import io.restassured.response.Response;
import io.restassured.specification.FilterableRequestSpecification;
import io.restassured.specification.FilterableResponseSpecification;
import org.springframework.restdocs.config.OperationPreprocessorsConfigurer;
/**
* A configurer that can be used to configure the operation preprocessors when using REST
* Assured 3.
*
* @author Filip Hrisafov
* @since 2.0.0
*/
public final class RestAssuredOperationPreprocessorsConfigurer extends
OperationPreprocessorsConfigurer<RestAssuredRestDocumentationConfigurer, RestAssuredOperationPreprocessorsConfigurer>
implements Filter {
RestAssuredOperationPreprocessorsConfigurer(
RestAssuredRestDocumentationConfigurer parent) {
super(parent);
}
@Override
public Response filter(FilterableRequestSpecification requestSpec,
FilterableResponseSpecification responseSpec, FilterContext context) {
return and().filter(requestSpec, responseSpec, context);
}
}

View File

@@ -33,15 +33,19 @@ import org.springframework.restdocs.config.RestDocumentationConfigurer;
* A REST Assured 3-specific {@link RestDocumentationConfigurer}.
*
* @author Andy Wilkinson
* @author Filip Hrisafov
* @since 1.2.0
*/
public final class RestAssuredRestDocumentationConfigurer extends
RestDocumentationConfigurer<RestAssuredSnippetConfigurer, RestAssuredRestDocumentationConfigurer>
RestDocumentationConfigurer<RestAssuredSnippetConfigurer, RestAssuredOperationPreprocessorsConfigurer, RestAssuredRestDocumentationConfigurer>
implements Filter {
private final RestAssuredSnippetConfigurer snippetConfigurer = new RestAssuredSnippetConfigurer(
this);
private final RestAssuredOperationPreprocessorsConfigurer operationPreprocessorsConfigurer = new RestAssuredOperationPreprocessorsConfigurer(
this);
private final RestDocumentationContextProvider contextProvider;
RestAssuredRestDocumentationConfigurer(
@@ -54,6 +58,11 @@ public final class RestAssuredRestDocumentationConfigurer extends
return this.snippetConfigurer;
}
@Override
public RestAssuredOperationPreprocessorsConfigurer operationPreprocessors() {
return this.operationPreprocessorsConfigurer;
}
@Override
public Response filter(FilterableRequestSpecification requestSpec,
FilterableResponseSpecification responseSpec, FilterContext filterContext) {

View File

@@ -97,6 +97,10 @@ public class RestDocumentationFilter implements Filter {
context);
configuration.remove(
RestDocumentationGenerator.ATTRIBUTE_NAME_DEFAULT_SNIPPETS);
configuration.remove(
RestDocumentationGenerator.ATTRIBUTE_NAME_DEFAULT_OPERATION_REQUEST_PREPROCESSOR);
configuration.remove(
RestDocumentationGenerator.ATTRIBUTE_NAME_DEFAULT_OPERATION_RESPONSE_PREPROCESSOR);
return configuration;
}

View File

@@ -28,6 +28,9 @@ import org.mockito.ArgumentCaptor;
import org.springframework.restdocs.JUnitRestDocumentation;
import org.springframework.restdocs.generate.RestDocumentationGenerator;
import org.springframework.restdocs.operation.preprocess.OperationRequestPreprocessor;
import org.springframework.restdocs.operation.preprocess.OperationResponsePreprocessor;
import org.springframework.restdocs.operation.preprocess.Preprocessors;
import org.springframework.restdocs.snippet.WriterResolver;
import org.springframework.restdocs.templates.TemplateEngine;
@@ -43,6 +46,7 @@ import static org.mockito.Mockito.verify;
* Tests for {@link RestAssuredRestDocumentationConfigurer}.
*
* @author Andy Wilkinson
* @author Filip Hrisafov
*/
public class RestAssuredRestDocumentationConfigurerTests {
@@ -68,7 +72,10 @@ public class RestAssuredRestDocumentationConfigurerTests {
@Test
public void configurationIsAddedToTheContext() {
this.configurer.filter(this.requestSpec, this.responseSpec, this.filterContext);
this.configurer.operationPreprocessors()
.withRequestDefaults(Preprocessors.prettyPrint())
.withResponseDefaults(Preprocessors.removeHeaders("Foo"))
.filter(this.requestSpec, this.responseSpec, this.filterContext);
@SuppressWarnings("rawtypes")
ArgumentCaptor<Map> configurationCaptor = ArgumentCaptor.forClass(Map.class);
verify(this.filterContext).setValue(
@@ -84,6 +91,14 @@ public class RestAssuredRestDocumentationConfigurerTests {
hasEntry(
equalTo(RestDocumentationGenerator.ATTRIBUTE_NAME_DEFAULT_SNIPPETS),
instanceOf(List.class)));
assertThat(configuration,
hasEntry(
equalTo(RestDocumentationGenerator.ATTRIBUTE_NAME_DEFAULT_OPERATION_REQUEST_PREPROCESSOR),
instanceOf(OperationRequestPreprocessor.class)));
assertThat(configuration,
hasEntry(
equalTo(RestDocumentationGenerator.ATTRIBUTE_NAME_DEFAULT_OPERATION_RESPONSE_PREPROCESSOR),
instanceOf(OperationResponsePreprocessor.class)));
}
}

View File

@@ -71,6 +71,7 @@ import static org.springframework.restdocs.test.SnippetMatchers.snippet;
*
* @author Andy Wilkinson
* @author Tomasz Kopczynski
* @author Filip Hrisafov
*/
public class RestAssuredRestDocumentationIntegrationTests {
@@ -327,6 +328,33 @@ public class RestAssuredRestDocumentationIntegrationTests {
.header("Host", "localhost").content(prettyPrinted))));
}
@Test
public void defaultPreprocessedRequest() throws Exception {
Pattern pattern = Pattern.compile("(\"alpha\")");
given().port(tomcat.getPort())
.filter(documentationConfiguration(this.restDocumentation)
.operationPreprocessors().withRequestDefaults(
prettyPrint(), replacePattern(pattern, "\"<<beta>>\""),
modifyUris().removePort(),
removeHeaders("a", HttpHeaders.CONTENT_LENGTH)))
.header("a", "alpha").header("b", "bravo").contentType("application/json")
.accept("application/json").body("{\"a\":\"alpha\"}")
.filter(document("default-preprocessed-request")).get("/").then()
.statusCode(200);
String prettyPrinted = String.format("{%n \"a\" : \"<<beta>>\"%n}");
assertThat(
new File(
"build/generated-snippets/default-preprocessed-request/http-request.adoc"),
is(snippet(asciidoctor())
.withContents(
httpRequest(asciidoctor(), RequestMethod.GET, "/")
.header("b", "bravo")
.header("Accept",
MediaType.APPLICATION_JSON_VALUE)
.header("Content-Type", "application/json; charset=UTF-8")
.header("Host", "localhost").content(prettyPrinted))));
}
@Test
public void preprocessedResponse() throws Exception {
Pattern pattern = Pattern.compile("(\"alpha\")");
@@ -353,6 +381,33 @@ public class RestAssuredRestDocumentationIntegrationTests {
.content(prettyPrinted))));
}
@Test
public void defaultPreprocessedResponse() throws Exception {
Pattern pattern = Pattern.compile("(\"alpha\")");
given().port(tomcat.getPort())
.filter(documentationConfiguration(this.restDocumentation)
.operationPreprocessors()
.withResponseDefaults(prettyPrint(), maskLinks(),
removeHeaders("a", "Transfer-Encoding", "Date", "Server"),
replacePattern(pattern, "\"<<beta>>\""),
modifyUris().scheme("https").host("api.example.com")
.removePort()))
.filter(document("default-preprocessed-response")).get("/").then()
.statusCode(200);
String prettyPrinted = String.format("{%n \"a\" : \"<<beta>>\",%n \"links\" : "
+ "[ {%n \"rel\" : \"rel\",%n \"href\" : \"...\"%n } ]%n}");
assertThat(
new File(
"build/generated-snippets/default-preprocessed-response/http-response.adoc"),
is(snippet(asciidoctor())
.withContents(httpResponse(asciidoctor(), HttpStatus.OK)
.header("Foo", "https://api.example.com/foo/bar")
.header("Content-Type", "application/json;charset=UTF-8")
.header(HttpHeaders.CONTENT_LENGTH,
prettyPrinted.getBytes().length)
.content(prettyPrinted))));
}
@Test
public void customSnippetTemplate() throws Exception {
ClassLoader classLoader = new URLClassLoader(new URL[] {