diff --git a/docs/src/docs/asciidoc/customizing-requests-and-responses.adoc b/docs/src/docs/asciidoc/customizing-requests-and-responses.adoc new file mode 100644 index 00000000..524f3afa --- /dev/null +++ b/docs/src/docs/asciidoc/customizing-requests-and-responses.adoc @@ -0,0 +1,56 @@ +[[customizing-requests-and-responses]] +== Customizing requests and responses + +There may be situations where you do not want to document a request exactly as it was sent +or a response exactly as it was received. Spring REST Docs provides a number of +preprocessors that can be used to modify a request or response before it's documented. + +Preprocessing is configured by calling `document` with an `OperationRequestPreprocessor`, +and/or an `OperationResponsePreprocessor`. Instances can be obtained using the +static `preprocessRequest` and `preprocessResponse` methods on `Preprocessors`: + +[source,java,indent=0] +---- +include::{examples-dir}/com/example/Preprocessing.java[tags=general] +---- +<1> Apply a request preprocessor that will remove the header named `Foo`. +<2> Apply a response preprocessor that will pretty print its content. + +Various built in preprocessors, including those illustrated above, are available via the +static methods on `Preprocessors`. See below for further details. + + + +[[customizing-requests-and-responses-pretty-printing]] +=== Pretty printing + +`prettyPrint` on `Preprocessors` formats the content of the request or response +to make it easier to read. + + + +[[customizing-requests-and-responses-masking-links]] +=== Masking links + +If you're documenting a Hypermedia-based API, you may want to encourage clients to +navigate the API using links rather than through the use of hard coded URIs. One way to do +this is to limit the use of URIs in the documentation. `maskLinks` on +`Preprocessors` replaces the `href` of any links in the response with `...`. A +different replacement can also be specified if you wish. + + + +[[customizing-requests-and-responses-removing-headers]] +=== Removing headers + +`removeHeaders` on `Preprocessors` removes any occurrences of the named headers +from the request or response. + + + +[[customizing-requests-and-responses-replacing-patterns]] +=== Replacing patterns + +`replacePattern` on `Preprocessors` provides a general purpose mechanism for +replacing content in a request or response. Any occurrences of a regular expression are +replaced. \ No newline at end of file diff --git a/docs/src/docs/asciidoc/customizing-responses.adoc b/docs/src/docs/asciidoc/customizing-responses.adoc deleted file mode 100644 index fe2eb415..00000000 --- a/docs/src/docs/asciidoc/customizing-responses.adoc +++ /dev/null @@ -1,45 +0,0 @@ -[[customizing-responses]] -== Customizing responses - -There may be situations where you do not want to document a response exactly as received. -Spring REST Docs provides a number of response post processors that can be used to modify -a response after it is received but before it's documented. - -Response modification is configured using a `ResponseModifier`. An instance can be -obtained using the static `modifyResponseTo` method on `RestDocumentation`. Once the -response modifications have been provided, documentation can be configured as usual -via the `andDocument` method: - -[source,java,indent=0] ----- -include::{examples-dir}/com/example/ResponsePostProcessing.java[tags=general] ----- -<1> Call `modifyResponseTo` to configure response modifications, passing in one or more -`ResponsePostProcessor` implementations. -<2> Proceed with documenting the call. - - -[[customizing-responses-pretty-printing]] -=== Pretty printing - -`prettyPrintContent` on `ResponsePostProcessors` formats the body of the response to -make it easier to read. - -[[customizing-responses-masking-links]] -=== Masking links - -If you're documenting a Hypermedia-based API, you may want to encourage clients to -navigate the API using links rather than through the use of hard coded URIs. One way to do -this is to limit the use of URIs in the documentation. `maskLinks` on -`ResponsePostProcessors` replaces the `href` of any links in the response with `...`. A -different replacement can also be specified if you wish. - -=== Removing headers - -`removeHeaders` on `ResponsePostProcessors` removes any occurrences of the named headers -from the response. - -=== Replacing patterns - -`replacePattern` on `ResponsePostProcessors` provides a general purpose mechanism for -replacing content in a response. Any occurrences of a regular expression are replaced. \ No newline at end of file diff --git a/docs/src/docs/asciidoc/index.adoc b/docs/src/docs/asciidoc/index.adoc index 2fb04b36..d4821b02 100644 --- a/docs/src/docs/asciidoc/index.adoc +++ b/docs/src/docs/asciidoc/index.adoc @@ -23,7 +23,7 @@ snippets produced with Spring MVC Test. include::introduction.adoc[] include::getting-started.adoc[] include::documenting-your-api.adoc[] -include::customizing-responses.adoc[] +include::customizing-requests-and-responses.adoc[] include::configuration.adoc[] include::working-with-asciidoctor.adoc[] include::contributing.adoc[] \ No newline at end of file diff --git a/docs/src/test/java/com/example/ResponsePostProcessing.java b/docs/src/test/java/com/example/Preprocessing.java similarity index 63% rename from docs/src/test/java/com/example/ResponsePostProcessing.java rename to docs/src/test/java/com/example/Preprocessing.java index a0c8d8ae..cf2d9ad2 100644 --- a/docs/src/test/java/com/example/ResponsePostProcessing.java +++ b/docs/src/test/java/com/example/Preprocessing.java @@ -16,13 +16,17 @@ package com.example; -import static org.springframework.restdocs.RestDocumentation.modifyResponseTo; import static org.springframework.restdocs.RestDocumentationRequestBuilders.get; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +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.removeHeaders; +import static org.springframework.restdocs.operation.preprocess.Preprocessors.prettyPrint; +import static org.springframework.restdocs.RestDocumentation.document; import org.springframework.test.web.servlet.MockMvc; -public class ResponsePostProcessing { +public class Preprocessing { private MockMvc mockMvc; @@ -30,8 +34,9 @@ public class ResponsePostProcessing { // tag::general[] this.mockMvc.perform(get("/")) .andExpect(status().isOk()) - .andDo(modifyResponseTo(/* ... */) // <1> - .andDocument("index")); // <2> + .andDo(document("index", + preprocessRequest(removeHeaders("Foo")), // <1> + preprocessResponse(prettyPrint()))); // <2> // end::general[] } diff --git a/spring-restdocs/src/main/java/org/springframework/restdocs/ResponseModifier.java b/spring-restdocs/src/main/java/org/springframework/restdocs/ResponseModifier.java deleted file mode 100644 index a72ded80..00000000 --- a/spring-restdocs/src/main/java/org/springframework/restdocs/ResponseModifier.java +++ /dev/null @@ -1,123 +0,0 @@ -/* - * Copyright 2014-2015 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; - -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.util.Arrays; -import java.util.List; - -import org.springframework.cglib.proxy.Enhancer; -import org.springframework.cglib.proxy.MethodInterceptor; -import org.springframework.cglib.proxy.MethodProxy; -import org.springframework.core.BridgeMethodResolver; -import org.springframework.mock.web.MockHttpServletResponse; -import org.springframework.restdocs.response.ResponsePostProcessor; -import org.springframework.restdocs.snippet.Snippet; -import org.springframework.test.web.servlet.MvcResult; -import org.springframework.util.ReflectionUtils; - -/** - * Modifies the response in an {@link MvcResult} by applying {@link ResponsePostProcessor - * ResponsePostProcessors} to it. - * - * @see RestDocumentation#modifyResponseTo(ResponsePostProcessor...) - * @author Andy Wilkinson - */ -public final class ResponseModifier { - - private final List postProcessors; - - ResponseModifier(ResponsePostProcessor... postProcessors) { - this.postProcessors = Arrays.asList(postProcessors); - } - - /** - * Provides a {@link RestDocumentationResultHandler} that can be used to document the - * request and modified response. - * @param identifier an identifier for the API call that is being documented - * @param snippets the snippets to use to document the call - * @return the result handler that will produce the documentation - */ - public RestDocumentationResultHandler andDocument(String identifier, - Snippet... snippets) { - return new ResponseModifyingRestDocumentationResultHandler(identifier, snippets); - } - - class ResponseModifyingRestDocumentationResultHandler extends - RestDocumentationResultHandler { - - private ResponseModifyingRestDocumentationResultHandler(String identifier, - Snippet... snippets) { - super(identifier, snippets); - } - - @Override - public void handle(MvcResult result) throws Exception { - super.handle(postProcessResponse(result)); - } - - MvcResult postProcessResponse(MvcResult result) throws Exception { - MockHttpServletResponse response = result.getResponse(); - for (ResponsePostProcessor postProcessor : ResponseModifier.this.postProcessors) { - response = postProcessor.postProcess(response); - } - return decorateResult(result, response); - } - - private MvcResult decorateResult(MvcResult result, - MockHttpServletResponse response) { - Enhancer enhancer = new Enhancer(); - enhancer.setSuperclass(MvcResult.class); - enhancer.setCallback(new GetResponseMethodInterceptor(response, result)); - return (MvcResult) enhancer.create(); - } - - private class GetResponseMethodInterceptor implements MethodInterceptor { - - private final MvcResult delegate; - - private final MockHttpServletResponse response; - - private final Method getResponseMethod = findMethod("getResponse"); - - private GetResponseMethodInterceptor(MockHttpServletResponse response, - MvcResult delegate) { - this.delegate = delegate; - this.response = response; - } - - @Override - public Object intercept(Object proxy, Method method, Object[] args, - MethodProxy methodProxy) throws IllegalAccessException, - InvocationTargetException { - if (this.getResponseMethod.equals(method)) { - return this.response; - } - return method.invoke(this.delegate, args); - } - - private Method findMethod(String methodName) { - return BridgeMethodResolver.findBridgedMethod(ReflectionUtils.findMethod( - MvcResult.class, methodName)); - } - - } - - } - -} diff --git a/spring-restdocs/src/main/java/org/springframework/restdocs/RestDocumentation.java b/spring-restdocs/src/main/java/org/springframework/restdocs/RestDocumentation.java index d5977b3c..952ca423 100644 --- a/spring-restdocs/src/main/java/org/springframework/restdocs/RestDocumentation.java +++ b/spring-restdocs/src/main/java/org/springframework/restdocs/RestDocumentation.java @@ -17,11 +17,10 @@ package org.springframework.restdocs; import org.springframework.restdocs.config.RestDocumentationConfigurer; -import org.springframework.restdocs.response.ResponsePostProcessor; -import org.springframework.restdocs.response.ResponsePostProcessors; +import org.springframework.restdocs.operation.preprocess.OperationRequestPreprocessor; +import org.springframework.restdocs.operation.preprocess.OperationResponsePreprocessor; import org.springframework.restdocs.snippet.Snippet; import org.springframework.test.web.servlet.MockMvc; -import org.springframework.test.web.servlet.MvcResult; import org.springframework.test.web.servlet.ResultActions; import org.springframework.test.web.servlet.setup.ConfigurableMockMvcBuilder; import org.springframework.test.web.servlet.setup.MockMvcConfigurer; @@ -50,7 +49,7 @@ public abstract class RestDocumentation { /** * Documents the API call with the given {@code identifier} using the given - * {@code handlers}. + * {@code snippets}. * * @param identifier an identifier for the API call that is being documented * @param snippets the snippets that will document the API call @@ -64,17 +63,60 @@ public abstract class RestDocumentation { } /** - * Enables the modification of the response in a {@link MvcResult} prior to it being - * documented. The modification is performed using the given - * {@code responsePostProcessors}. + * Documents the API call with the given {@code identifier} using the given + * {@code snippets}. The given {@code requestPreprocessor} is applied to the request + * before it is documented. * - * @param responsePostProcessors the post-processors to use to modify the response - * @return the response modifier - * @see ResponsePostProcessors + * @param identifier an identifier for the API call that is being documented + * @param requestPreprocessor the request preprocessor + * @param snippets the snippets that will document the API call + * @return a Mock MVC {@code ResultHandler} that will produce the documentation + * @see MockMvc#perform(org.springframework.test.web.servlet.RequestBuilder) + * @see ResultActions#andDo(org.springframework.test.web.servlet.ResultHandler) */ - public static ResponseModifier modifyResponseTo( - ResponsePostProcessor... responsePostProcessors) { - return new ResponseModifier(responsePostProcessors); + public static RestDocumentationResultHandler document(String identifier, + OperationRequestPreprocessor requestPreprocessor, Snippet... snippets) { + return new RestDocumentationResultHandler(identifier, requestPreprocessor, + snippets); + } + + /** + * Documents the API call with the given {@code identifier} using the given + * {@code snippets}. The given {@code responsePreprocessor} is applied to the request + * before it is documented. + * + * @param identifier an identifier for the API call that is being documented + * @param responsePreprocessor the response preprocessor + * @param snippets the snippets that will document the API call + * @return a Mock MVC {@code ResultHandler} that will produce the documentation + * @see MockMvc#perform(org.springframework.test.web.servlet.RequestBuilder) + * @see ResultActions#andDo(org.springframework.test.web.servlet.ResultHandler) + */ + public static RestDocumentationResultHandler document(String identifier, + OperationResponsePreprocessor responsePreprocessor, Snippet... snippets) { + return new RestDocumentationResultHandler(identifier, responsePreprocessor, + snippets); + } + + /** + * Documents the API call with the given {@code identifier} using the given + * {@code snippets}. The given {@code requestPreprocessor} and + * {@code responsePreprocessor} are applied to the request and response respectively + * before they are documented. + * + * @param identifier an identifier for the API call that is being documented + * @param requestPreprocessor the request preprocessor + * @param responsePreprocessor the response preprocessor + * @param snippets the snippets that will document the API call + * @return a Mock MVC {@code ResultHandler} that will produce the documentation + * @see MockMvc#perform(org.springframework.test.web.servlet.RequestBuilder) + * @see ResultActions#andDo(org.springframework.test.web.servlet.ResultHandler) + */ + public static RestDocumentationResultHandler document(String identifier, + OperationRequestPreprocessor requestPreprocessor, + OperationResponsePreprocessor responsePreprocessor, Snippet... snippets) { + return new RestDocumentationResultHandler(identifier, requestPreprocessor, + responsePreprocessor, snippets); } } diff --git a/spring-restdocs/src/main/java/org/springframework/restdocs/RestDocumentationResultHandler.java b/spring-restdocs/src/main/java/org/springframework/restdocs/RestDocumentationResultHandler.java index 71bca2b5..e1fc99f1 100644 --- a/spring-restdocs/src/main/java/org/springframework/restdocs/RestDocumentationResultHandler.java +++ b/spring-restdocs/src/main/java/org/springframework/restdocs/RestDocumentationResultHandler.java @@ -26,10 +26,16 @@ import java.util.Map; import org.springframework.restdocs.operation.MockMvcOperationRequestFactory; import org.springframework.restdocs.operation.MockMvcOperationResponseFactory; +import org.springframework.restdocs.operation.Operation; +import org.springframework.restdocs.operation.OperationRequest; +import org.springframework.restdocs.operation.OperationResponse; import org.springframework.restdocs.operation.StandardOperation; +import org.springframework.restdocs.operation.preprocess.OperationRequestPreprocessor; +import org.springframework.restdocs.operation.preprocess.OperationResponsePreprocessor; import org.springframework.restdocs.snippet.Snippet; import org.springframework.test.web.servlet.MvcResult; import org.springframework.test.web.servlet.ResultHandler; +import org.springframework.util.Assert; /** * A Spring MVC Test {@code ResultHandler} for documenting RESTful APIs. @@ -42,10 +48,39 @@ public class RestDocumentationResultHandler implements ResultHandler { private final String identifier; + private final OperationRequestPreprocessor requestPreprocessor; + + private final OperationResponsePreprocessor responsePreprocessor; + private final List snippets; RestDocumentationResultHandler(String identifier, Snippet... snippets) { + this(identifier, new IdentityOperationRequestPreprocessor(), + new IdentityOperationResponsePreprocessor(), snippets); + } + + RestDocumentationResultHandler(String identifier, + OperationRequestPreprocessor requestPreprocessor, Snippet... snippets) { + this(identifier, requestPreprocessor, + new IdentityOperationResponsePreprocessor(), snippets); + } + + RestDocumentationResultHandler(String identifier, + OperationResponsePreprocessor responsePreprocessor, Snippet... snippets) { + this(identifier, new IdentityOperationRequestPreprocessor(), + responsePreprocessor, snippets); + } + + RestDocumentationResultHandler(String identifier, + OperationRequestPreprocessor requestPreprocessor, + OperationResponsePreprocessor responsePreprocessor, Snippet... snippets) { + Assert.notNull(identifier, "identifier must be non-null"); + Assert.notNull(requestPreprocessor, "requestPreprocessor must be non-null"); + Assert.notNull(responsePreprocessor, "responsePreprocessor must be non-null"); + Assert.notNull(snippets, "snippets must be non-null"); this.identifier = identifier; + this.requestPreprocessor = requestPreprocessor; + this.responsePreprocessor = responsePreprocessor; this.snippets = Arrays.asList(snippets); } @@ -55,11 +90,15 @@ public class RestDocumentationResultHandler implements ResultHandler { for (String name : iterable(result.getRequest().getAttributeNames())) { attributes.put(name, result.getRequest().getAttribute(name)); } - StandardOperation operation = new StandardOperation(this.identifier, - new MockMvcOperationRequestFactory().createOperationRequest(result - .getRequest()), - new MockMvcOperationResponseFactory().createOperationResponse(result - .getResponse()), attributes); + OperationRequest request = this.requestPreprocessor + .preprocess(new MockMvcOperationRequestFactory() + .createOperationRequest(result.getRequest())); + + OperationResponse response = this.responsePreprocessor + .preprocess(new MockMvcOperationResponseFactory() + .createOperationResponse(result.getResponse())); + Operation operation = new StandardOperation(this.identifier, request, response, + attributes); for (Snippet snippet : getSnippets(result)) { snippet.document(operation); } @@ -74,4 +113,24 @@ public class RestDocumentationResultHandler implements ResultHandler { return combinedSnippets; } + static final class IdentityOperationRequestPreprocessor implements + OperationRequestPreprocessor { + + @Override + public OperationRequest preprocess(OperationRequest request) { + return request; + } + + } + + static final class IdentityOperationResponsePreprocessor implements + OperationResponsePreprocessor { + + @Override + public OperationResponse preprocess(OperationResponse response) { + return response; + } + + } + } diff --git a/spring-restdocs/src/main/java/org/springframework/restdocs/operation/StandardOperationRequest.java b/spring-restdocs/src/main/java/org/springframework/restdocs/operation/StandardOperationRequest.java index 37ed936f..b4bac39c 100644 --- a/spring-restdocs/src/main/java/org/springframework/restdocs/operation/StandardOperationRequest.java +++ b/spring-restdocs/src/main/java/org/springframework/restdocs/operation/StandardOperationRequest.java @@ -59,7 +59,7 @@ public class StandardOperationRequest implements OperationRequest { Collection parts) { this.uri = uri; this.method = method; - this.content = content == null ? new byte[0] : content; + this.content = content; this.headers = headers; this.parameters = parameters; this.parts = parts; diff --git a/spring-restdocs/src/main/java/org/springframework/restdocs/operation/StandardOperationResponse.java b/spring-restdocs/src/main/java/org/springframework/restdocs/operation/StandardOperationResponse.java index f7a1c594..c8fcf401 100644 --- a/spring-restdocs/src/main/java/org/springframework/restdocs/operation/StandardOperationResponse.java +++ b/spring-restdocs/src/main/java/org/springframework/restdocs/operation/StandardOperationResponse.java @@ -44,7 +44,7 @@ public class StandardOperationResponse implements OperationResponse { byte[] content) { this.status = status; this.headers = headers; - this.content = content == null ? new byte[0] : content; + this.content = content; } @Override diff --git a/spring-restdocs/src/main/java/org/springframework/restdocs/response/ResponsePostProcessor.java b/spring-restdocs/src/main/java/org/springframework/restdocs/operation/preprocess/ContentModifier.java similarity index 50% rename from spring-restdocs/src/main/java/org/springframework/restdocs/response/ResponsePostProcessor.java rename to spring-restdocs/src/main/java/org/springframework/restdocs/operation/preprocess/ContentModifier.java index c685626f..52e1075d 100644 --- a/spring-restdocs/src/main/java/org/springframework/restdocs/response/ResponsePostProcessor.java +++ b/spring-restdocs/src/main/java/org/springframework/restdocs/operation/preprocess/ContentModifier.java @@ -14,26 +14,27 @@ * limitations under the License. */ -package org.springframework.restdocs.response; +package org.springframework.restdocs.operation.preprocess; -import org.springframework.mock.web.MockHttpServletResponse; +import org.springframework.restdocs.operation.OperationRequest; +import org.springframework.restdocs.operation.OperationResponse; /** - * A {@code ResponsePostProcessor} is used to modify the response received from a MockMvc - * call prior to the response being documented. + * A {@code ContentModifier} modifies the content of an {@link OperationRequest} or + * {@link OperationResponse} during the preprocessing that is performed prior to + * documentation generation. * * @author Andy Wilkinson + * @see ContentModifyingOperationPreprocessor */ -public interface ResponsePostProcessor { +interface ContentModifier { /** - * Post-processes the given {@code response}, returning a, possibly new, - * {@link MockHttpServletResponse} that should now be used. + * Returns modified content based on the given {@code originalContent} * - * @param response The response to post-process - * @return The result of the post-processing - * @throws Exception if a failure occurs during the post-processing + * @param originalContent the original content + * @return the modified content */ - MockHttpServletResponse postProcess(MockHttpServletResponse response) - throws Exception; + byte[] modifyContent(byte[] originalContent); + } diff --git a/spring-restdocs/src/main/java/org/springframework/restdocs/operation/preprocess/ContentModifyingOperationPreprocessor.java b/spring-restdocs/src/main/java/org/springframework/restdocs/operation/preprocess/ContentModifyingOperationPreprocessor.java new file mode 100644 index 00000000..1ff1e819 --- /dev/null +++ b/spring-restdocs/src/main/java/org/springframework/restdocs/operation/preprocess/ContentModifyingOperationPreprocessor.java @@ -0,0 +1,65 @@ +/* + * Copyright 2014-2015 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.operation.preprocess; + +import org.springframework.http.HttpHeaders; +import org.springframework.restdocs.operation.OperationRequest; +import org.springframework.restdocs.operation.OperationResponse; +import org.springframework.restdocs.operation.StandardOperationRequest; +import org.springframework.restdocs.operation.StandardOperationResponse; + +/** + * An {@link OperationPreprocessor} that applies a {@link ContentModifier} to the content + * of the request or response. + * + * @author Andy Wilkinson + */ +class ContentModifyingOperationPreprocessor implements OperationPreprocessor { + + private final ContentModifier contentModifier; + + ContentModifyingOperationPreprocessor(ContentModifier contentModifier) { + this.contentModifier = contentModifier; + } + + @Override + public OperationRequest preprocess(OperationRequest request) { + byte[] modifiedContent = this.contentModifier.modifyContent(request.getContent()); + return new StandardOperationRequest(request.getUri(), request.getMethod(), + modifiedContent, + getUpdatedHeaders(request.getHeaders(), modifiedContent), + request.getParameters(), request.getParts()); + } + + @Override + public OperationResponse preprocess(OperationResponse response) { + byte[] modifiedContent = this.contentModifier + .modifyContent(response.getContent()); + return new StandardOperationResponse(response.getStatus(), getUpdatedHeaders( + response.getHeaders(), modifiedContent), modifiedContent); + } + + private HttpHeaders getUpdatedHeaders(HttpHeaders headers, byte[] updatedContent) { + HttpHeaders updatedHeaders = new HttpHeaders(); + updatedHeaders.putAll(headers); + if (updatedHeaders.getContentLength() > -1) { + updatedHeaders.setContentLength(updatedContent.length); + } + return updatedHeaders; + } + +} diff --git a/spring-restdocs/src/main/java/org/springframework/restdocs/operation/preprocess/DelegatingOperationRequestPreprocessor.java b/spring-restdocs/src/main/java/org/springframework/restdocs/operation/preprocess/DelegatingOperationRequestPreprocessor.java new file mode 100644 index 00000000..ee57e83a --- /dev/null +++ b/spring-restdocs/src/main/java/org/springframework/restdocs/operation/preprocess/DelegatingOperationRequestPreprocessor.java @@ -0,0 +1,57 @@ +/* + * Copyright 2014-2015 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.operation.preprocess; + +import java.util.List; + +import org.springframework.restdocs.operation.OperationRequest; +import org.springframework.util.Assert; + +/** + * An {@link OperationRequestPreprocessor} that delgates to one or more + * {@link OperationPreprocessor OperationPreprocessors} to preprocess an + * {@link OperationRequest}. + * + * @author Andy Wilkinson + * + */ +class DelegatingOperationRequestPreprocessor implements OperationRequestPreprocessor { + + private final List delegates; + + /** + * Creates a new {@code DelegatingOperationRequestPreprocessor} that will delegate to + * the given {@code delegates} by calling + * {@link OperationPreprocessor#preprocess(OperationRequest)}. + * + * @param delegates the delegates + */ + DelegatingOperationRequestPreprocessor(List delegates) { + Assert.notNull(delegates, "delegates must be non-null"); + this.delegates = delegates; + } + + @Override + public OperationRequest preprocess(OperationRequest operationRequest) { + OperationRequest preprocessedRequest = operationRequest; + for (OperationPreprocessor delegate : this.delegates) { + preprocessedRequest = delegate.preprocess(preprocessedRequest); + } + return preprocessedRequest; + } + +} diff --git a/spring-restdocs/src/main/java/org/springframework/restdocs/operation/preprocess/DelegatingOperationResponsePreprocessor.java b/spring-restdocs/src/main/java/org/springframework/restdocs/operation/preprocess/DelegatingOperationResponsePreprocessor.java new file mode 100644 index 00000000..e200c25d --- /dev/null +++ b/spring-restdocs/src/main/java/org/springframework/restdocs/operation/preprocess/DelegatingOperationResponsePreprocessor.java @@ -0,0 +1,56 @@ +/* + * Copyright 2014-2015 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.operation.preprocess; + +import java.util.List; + +import org.springframework.restdocs.operation.OperationResponse; +import org.springframework.util.Assert; + +/** + * An {@link OperationResponsePreprocessor} that delgates to one or more + * {@link OperationPreprocessor OperationPreprocessors} to preprocess an + * {@link OperationResponse}. + * + * @author Andy Wilkinson + */ +class DelegatingOperationResponsePreprocessor implements OperationResponsePreprocessor { + + private final List delegates; + + /** + * Creates a new {@code DelegatingOperationResponsePreprocessor} that will delegate to + * the given {@code delegates} by calling + * {@link OperationPreprocessor#preprocess(OperationResponse)}. + * + * @param delegates the delegates + */ + DelegatingOperationResponsePreprocessor(List delegates) { + Assert.notNull(delegates, "delegates must be non-null"); + this.delegates = delegates; + } + + @Override + public OperationResponse preprocess(OperationResponse response) { + OperationResponse preprocessedResponse = response; + for (OperationPreprocessor delegate : this.delegates) { + preprocessedResponse = delegate.preprocess(preprocessedResponse); + } + return preprocessedResponse; + } + +} diff --git a/spring-restdocs/src/main/java/org/springframework/restdocs/operation/preprocess/HeaderRemovingOperationPreprocessor.java b/spring-restdocs/src/main/java/org/springframework/restdocs/operation/preprocess/HeaderRemovingOperationPreprocessor.java new file mode 100644 index 00000000..ef258a15 --- /dev/null +++ b/spring-restdocs/src/main/java/org/springframework/restdocs/operation/preprocess/HeaderRemovingOperationPreprocessor.java @@ -0,0 +1,63 @@ +/* + * Copyright 2014-2015 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.operation.preprocess; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; + +import org.springframework.http.HttpHeaders; +import org.springframework.restdocs.operation.OperationRequest; +import org.springframework.restdocs.operation.OperationResponse; +import org.springframework.restdocs.operation.StandardOperationRequest; +import org.springframework.restdocs.operation.StandardOperationResponse; + +/** + * An {@link OperationPreprocessor} that removes headers + * + * @author Andy Wilkinson + */ +class HeaderRemovingOperationPreprocessor implements OperationPreprocessor { + + private final Set headersToRemove; + + HeaderRemovingOperationPreprocessor(String... headersToRemove) { + this.headersToRemove = new HashSet<>(Arrays.asList(headersToRemove)); + } + + @Override + public OperationResponse preprocess(OperationResponse response) { + return new StandardOperationResponse(response.getStatus(), + removeHeaders(response.getHeaders()), response.getContent()); + } + + @Override + public OperationRequest preprocess(OperationRequest request) { + return new StandardOperationRequest(request.getUri(), request.getMethod(), + request.getContent(), removeHeaders(request.getHeaders()), + request.getParameters(), request.getParts()); + } + + private HttpHeaders removeHeaders(HttpHeaders originalHeaders) { + HttpHeaders processedHeaders = new HttpHeaders(); + processedHeaders.putAll(originalHeaders); + for (String headerToRemove : this.headersToRemove) { + processedHeaders.remove(headerToRemove); + } + return processedHeaders; + } +} diff --git a/spring-restdocs/src/main/java/org/springframework/restdocs/response/LinkMaskingResponsePostProcessor.java b/spring-restdocs/src/main/java/org/springframework/restdocs/operation/preprocess/LinkMaskingContentModifier.java similarity index 61% rename from spring-restdocs/src/main/java/org/springframework/restdocs/response/LinkMaskingResponsePostProcessor.java rename to spring-restdocs/src/main/java/org/springframework/restdocs/operation/preprocess/LinkMaskingContentModifier.java index 8396b163..74a5ff5f 100644 --- a/spring-restdocs/src/main/java/org/springframework/restdocs/response/LinkMaskingResponsePostProcessor.java +++ b/spring-restdocs/src/main/java/org/springframework/restdocs/operation/preprocess/LinkMaskingContentModifier.java @@ -14,30 +14,35 @@ * limitations under the License. */ -package org.springframework.restdocs.response; +package org.springframework.restdocs.operation.preprocess; import java.util.regex.Pattern; /** - * A {@link ResponsePostProcessor} that modifies the content of a hypermedia response to - * mask the hrefs of any links. + * A content modifier the masks the {@code href} of any hypermedia links * * @author Andy Wilkinson - * @author Dewet Diener */ -class LinkMaskingResponsePostProcessor extends PatternReplacingResponsePostProcessor { +class LinkMaskingContentModifier implements ContentModifier { private static final String DEFAULT_MASK = "..."; private static final Pattern LINK_HREF = Pattern.compile( "\"href\"\\s*:\\s*\"(.*?)\"", Pattern.DOTALL); - LinkMaskingResponsePostProcessor() { - super(LINK_HREF, DEFAULT_MASK); + private final ContentModifier contentModifier; + + LinkMaskingContentModifier() { + this(DEFAULT_MASK); } - LinkMaskingResponsePostProcessor(String mask) { - super(LINK_HREF, mask); + LinkMaskingContentModifier(String mask) { + this.contentModifier = new PatternReplacingContentModifier(LINK_HREF, mask); + } + + @Override + public byte[] modifyContent(byte[] originalContent) { + return this.contentModifier.modifyContent(originalContent); } } diff --git a/spring-restdocs/src/main/java/org/springframework/restdocs/operation/preprocess/OperationPreprocessor.java b/spring-restdocs/src/main/java/org/springframework/restdocs/operation/preprocess/OperationPreprocessor.java new file mode 100644 index 00000000..e28bcadd --- /dev/null +++ b/spring-restdocs/src/main/java/org/springframework/restdocs/operation/preprocess/OperationPreprocessor.java @@ -0,0 +1,47 @@ +/* + * Copyright 2014-2015 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.operation.preprocess; + +import org.springframework.restdocs.operation.Operation; +import org.springframework.restdocs.operation.OperationRequest; +import org.springframework.restdocs.operation.OperationResponse; + +/** + * An {@code OperationPreprocessor} processes the {@link OperationRequest} and + * {@link OperationResponse} of an {@link Operation} prior to it being documented. + * + * @author Andy Wilkinson + */ +public interface OperationPreprocessor { + + /** + * Processes the given {@code request} + * + * @param request the request to process + * @return the processed request + */ + OperationRequest preprocess(OperationRequest request); + + /** + * Processes the given {@code response} + * + * @param response the response to process + * @return the processed response + */ + OperationResponse preprocess(OperationResponse response); + +} diff --git a/spring-restdocs/src/main/java/org/springframework/restdocs/operation/preprocess/OperationRequestPreprocessor.java b/spring-restdocs/src/main/java/org/springframework/restdocs/operation/preprocess/OperationRequestPreprocessor.java new file mode 100644 index 00000000..39fd2132 --- /dev/null +++ b/spring-restdocs/src/main/java/org/springframework/restdocs/operation/preprocess/OperationRequestPreprocessor.java @@ -0,0 +1,38 @@ +/* + * Copyright 2014-2015 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.operation.preprocess; + +import org.springframework.restdocs.operation.OperationRequest; + +/** + * An {@code OperationRequestPreprocessor} is used to modify an {@code OperationRequest} + * prior to it being documented. + * + * @author Andy Wilkinson + */ +public interface OperationRequestPreprocessor { + + /** + * Processes and potentially modifies the given {@code request} before it is + * documented. + * + * @param request the request + * @return the modified request + */ + OperationRequest preprocess(OperationRequest request); + +} diff --git a/spring-restdocs/src/main/java/org/springframework/restdocs/operation/preprocess/OperationResponsePreprocessor.java b/spring-restdocs/src/main/java/org/springframework/restdocs/operation/preprocess/OperationResponsePreprocessor.java new file mode 100644 index 00000000..a8ea46fc --- /dev/null +++ b/spring-restdocs/src/main/java/org/springframework/restdocs/operation/preprocess/OperationResponsePreprocessor.java @@ -0,0 +1,22 @@ +package org.springframework.restdocs.operation.preprocess; + +import org.springframework.restdocs.operation.OperationResponse; + +/** + * An {@code OperationRequestPreprocessor} is used to modify an {@code OperationRequest} + * prior to it being documented. + * + * @author Andy Wilkinson + */ +public interface OperationResponsePreprocessor { + + /** + * Processes and potentially modifies the given {@code response} before it is + * documented. + * + * @param response the response + * @return the modified response + */ + OperationResponse preprocess(OperationResponse response); + +} diff --git a/spring-restdocs/src/main/java/org/springframework/restdocs/response/PatternReplacingResponsePostProcessor.java b/spring-restdocs/src/main/java/org/springframework/restdocs/operation/preprocess/PatternReplacingContentModifier.java similarity index 54% rename from spring-restdocs/src/main/java/org/springframework/restdocs/response/PatternReplacingResponsePostProcessor.java rename to spring-restdocs/src/main/java/org/springframework/restdocs/operation/preprocess/PatternReplacingContentModifier.java index ce640341..0b702f91 100644 --- a/spring-restdocs/src/main/java/org/springframework/restdocs/response/PatternReplacingResponsePostProcessor.java +++ b/spring-restdocs/src/main/java/org/springframework/restdocs/operation/preprocess/PatternReplacingContentModifier.java @@ -14,43 +14,51 @@ * limitations under the License. */ -package org.springframework.restdocs.response; +package org.springframework.restdocs.operation.preprocess; import java.util.regex.Matcher; import java.util.regex.Pattern; /** - * A {@link ResponsePostProcessor} that modifies the content of the response by replacing - * occurrences of a regular expression {@link Pattern}. + * A {@link ContentModifier} that modifies the content by replacing occurrences of a + * regular expression {@link Pattern}. * * @author Andy Wilkinson * @author Dewet Diener */ -class PatternReplacingResponsePostProcessor extends ContentModifyingReponsePostProcessor { +class PatternReplacingContentModifier implements ContentModifier { private final Pattern pattern; private final String replacement; - PatternReplacingResponsePostProcessor(Pattern pattern, String replacement) { + /** + * Creates a new {@link PatternReplacingContentModifier} that will replace occurences + * the given {@code pattern} with the given {@code replacement}. + * + * @param pattern the pattern + * @param replacement the replacement + */ + PatternReplacingContentModifier(Pattern pattern, String replacement) { this.pattern = pattern; this.replacement = replacement; } @Override - protected String modifyContent(String originalContent) { - Matcher matcher = this.pattern.matcher(originalContent); + public byte[] modifyContent(byte[] content) { + String original = new String(content); + Matcher matcher = this.pattern.matcher(original); StringBuilder buffer = new StringBuilder(); int previous = 0; while (matcher.find()) { - buffer.append(originalContent.substring(previous, matcher.start(1))); + buffer.append(original.substring(previous, matcher.start(1))); buffer.append(this.replacement); previous = matcher.end(1); } - if (previous < originalContent.length()) { - buffer.append(originalContent.substring(previous)); + if (previous < original.length()) { + buffer.append(original.substring(previous)); } - return buffer.toString(); + return buffer.toString().getBytes(); } } diff --git a/spring-restdocs/src/main/java/org/springframework/restdocs/operation/preprocess/Preprocessors.java b/spring-restdocs/src/main/java/org/springframework/restdocs/operation/preprocess/Preprocessors.java new file mode 100644 index 00000000..563b8b53 --- /dev/null +++ b/spring-restdocs/src/main/java/org/springframework/restdocs/operation/preprocess/Preprocessors.java @@ -0,0 +1,122 @@ +/* + * Copyright 2014-2015 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.operation.preprocess; + +import java.util.Arrays; +import java.util.regex.Pattern; + +import org.springframework.restdocs.operation.Operation; +import org.springframework.restdocs.operation.OperationRequest; +import org.springframework.restdocs.operation.OperationResponse; + +/** + * Static factory methods for creating {@link OperationPreprocessor + * OperationPreprocessors} that can be applied to an {@link Operation Operation's} + * {@link OperationRequest request} or {@link OperationResponse response} before it is + * documented. + * + * @author Andy Wilkinson + */ +public class Preprocessors { + + private Preprocessors() { + + } + + /** + * Returns an {@link OperationRequestPreprocessor} that will preprocess the request by + * applying the given {@code preprocessors} to it. + * + * @param preprocessors the preprocessors + * @return the request preprocessor + */ + public static OperationRequestPreprocessor preprocessRequest( + OperationPreprocessor... preprocessors) { + return new DelegatingOperationRequestPreprocessor(Arrays.asList(preprocessors)); + } + + /** + * Returns an {@link OperationResponsePreprocessor} that will preprocess the response + * by applying the given {@code preprocessors} to it. + * + * @param preprocessors the preprocessors + * @return the response preprocessor + */ + public static OperationResponsePreprocessor preprocessResponse( + OperationPreprocessor... preprocessors) { + return new DelegatingOperationResponsePreprocessor(Arrays.asList(preprocessors)); + } + + /** + * Returns an {@code OperationPreprocessor} that will pretty print the content of the + * request or response. + * + * @return the preprocessor + */ + public static OperationPreprocessor prettyPrint() { + return new ContentModifyingOperationPreprocessor( + new PrettyPrintingContentModifier()); + } + + /** + * Returns an {@code OperationPreprocessor} that will remove headers from the request + * or response. + * + * @param headersToRemove the names of the headers to remove + * @return the preprocessor + */ + public static OperationPreprocessor removeHeaders(String... headersToRemove) { + return new HeaderRemovingOperationPreprocessor(headersToRemove); + } + + /** + * Returns an {@code OperationPreprocessor} that will mask the href of hypermedia + * links in the request or response. + * + * @return the preprocessor + */ + public static OperationPreprocessor maskLinks() { + return new ContentModifyingOperationPreprocessor(new LinkMaskingContentModifier()); + } + + /** + * Returns an {@code OperationPreprocessor} that will mask the href of hypermedia + * links in the request or response. + * + * @param mask the link mask + * @return the preprocessor + */ + public static OperationPreprocessor maskLinks(String mask) { + return new ContentModifyingOperationPreprocessor(new LinkMaskingContentModifier( + mask)); + } + + /** + * Returns an {@code OperationPreprocessor} that will modify the content of the + * request or response by replacing occurences of the given {@code pattern} with the + * given {@code replacement} + * + * @param pattern the pattern + * @param replacement the replacement + * @return the preprocessor + */ + public static OperationPreprocessor replacePattern(Pattern pattern, String replacement) { + return new ContentModifyingOperationPreprocessor( + new PatternReplacingContentModifier(pattern, replacement)); + } + +} diff --git a/spring-restdocs/src/main/java/org/springframework/restdocs/response/PrettyPrintingResponsePostProcessor.java b/spring-restdocs/src/main/java/org/springframework/restdocs/operation/preprocess/PrettyPrintingContentModifier.java similarity index 73% rename from spring-restdocs/src/main/java/org/springframework/restdocs/response/PrettyPrintingResponsePostProcessor.java rename to spring-restdocs/src/main/java/org/springframework/restdocs/operation/preprocess/PrettyPrintingContentModifier.java index 4e457c80..9e193b3e 100644 --- a/spring-restdocs/src/main/java/org/springframework/restdocs/response/PrettyPrintingResponsePostProcessor.java +++ b/spring-restdocs/src/main/java/org/springframework/restdocs/operation/preprocess/PrettyPrintingContentModifier.java @@ -14,10 +14,10 @@ * limitations under the License. */ -package org.springframework.restdocs.response; +package org.springframework.restdocs.operation.preprocess; +import java.io.ByteArrayInputStream; import java.io.IOException; -import java.io.StringReader; import java.io.StringWriter; import java.util.Arrays; import java.util.Collections; @@ -29,27 +29,28 @@ import javax.xml.transform.TransformerFactory; import javax.xml.transform.stream.StreamResult; import javax.xml.transform.stream.StreamSource; -import org.springframework.util.StringUtils; - import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializationFeature; -class PrettyPrintingResponsePostProcessor extends ContentModifyingReponsePostProcessor { +/** + * A {@link ContentModifier} that modifies the content by pretty printing it. + * + * @author Andy Wilkinson + */ +public class PrettyPrintingContentModifier implements ContentModifier { private static final List PRETTY_PRINTERS = Collections .unmodifiableList(Arrays.asList(new JsonPrettyPrinter(), new XmlPrettyPrinter())); @Override - protected String modifyContent(String originalContent) { - if (StringUtils.hasText(originalContent)) { - for (PrettyPrinter prettyPrinter : PRETTY_PRINTERS) { - try { - return prettyPrinter.prettyPrint(originalContent); - } - catch (Exception ex) { - // Continue - } + public byte[] modifyContent(byte[] originalContent) { + for (PrettyPrinter prettyPrinter : PRETTY_PRINTERS) { + try { + return prettyPrinter.prettyPrint(originalContent).getBytes(); + } + catch (Exception ex) { + // Continue } } return originalContent; @@ -57,21 +58,21 @@ class PrettyPrintingResponsePostProcessor extends ContentModifyingReponsePostPro private interface PrettyPrinter { - String prettyPrint(String string) throws Exception; + String prettyPrint(byte[] content) throws Exception; } private static final class XmlPrettyPrinter implements PrettyPrinter { @Override - public String prettyPrint(String original) throws Exception { + public String prettyPrint(byte[] original) throws Exception { Transformer transformer = TransformerFactory.newInstance().newTransformer(); transformer.setOutputProperty(OutputKeys.INDENT, "yes"); transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "4"); transformer.setOutputProperty(OutputKeys.DOCTYPE_PUBLIC, "yes"); StringWriter transformed = new StringWriter(); - transformer.transform(new StreamSource(new StringReader(original)), + transformer.transform(new StreamSource(new ByteArrayInputStream(original)), new StreamResult(transformed)); return transformed.toString(); } @@ -80,7 +81,7 @@ class PrettyPrintingResponsePostProcessor extends ContentModifyingReponsePostPro private static final class JsonPrettyPrinter implements PrettyPrinter { @Override - public String prettyPrint(String original) throws IOException { + public String prettyPrint(byte[] original) throws IOException { ObjectMapper objectMapper = new ObjectMapper().configure( SerializationFeature.INDENT_OUTPUT, true); return objectMapper.writeValueAsString(objectMapper.readTree(original)); diff --git a/spring-restdocs/src/main/java/org/springframework/restdocs/response/ContentModifyingReponsePostProcessor.java b/spring-restdocs/src/main/java/org/springframework/restdocs/response/ContentModifyingReponsePostProcessor.java deleted file mode 100644 index 52640b3f..00000000 --- a/spring-restdocs/src/main/java/org/springframework/restdocs/response/ContentModifyingReponsePostProcessor.java +++ /dev/null @@ -1,96 +0,0 @@ -/* - * Copyright 2014-2015 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.response; - -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; - -import org.springframework.cglib.proxy.Enhancer; -import org.springframework.cglib.proxy.MethodInterceptor; -import org.springframework.cglib.proxy.MethodProxy; -import org.springframework.core.BridgeMethodResolver; -import org.springframework.mock.web.MockHttpServletResponse; -import org.springframework.util.ReflectionUtils; - -/** - * A base class for {@link ResponsePostProcessor ResponsePostProcessors} that modify the - * content of the response. - * - * @author Andy Wilkinson - */ -public abstract class ContentModifyingReponsePostProcessor implements - ResponsePostProcessor { - - @Override - public MockHttpServletResponse postProcess(MockHttpServletResponse response) - throws Exception { - String modifiedContent = modifyContent(response.getContentAsString()); - - Enhancer enhancer = new Enhancer(); - enhancer.setSuperclass(MockHttpServletResponse.class); - enhancer.setCallback(new ContentModifyingMethodInterceptor(modifiedContent, - response)); - - return (MockHttpServletResponse) enhancer.create(); - } - - /** - * Returns a modified version of the given {@code originalContent} - * - * @param originalContent the content to modify - * @return the modified content - * @throws Exception if a failure occurs while modifying the content - */ - protected abstract String modifyContent(String originalContent) throws Exception; - - private static class ContentModifyingMethodInterceptor implements MethodInterceptor { - - private final Method getContentAsStringMethod = findMethod("getContentAsString"); - - private final Method getContentAsByteArray = findMethod("getContentAsByteArray"); - - private final String modifiedContent; - - private final MockHttpServletResponse delegate; - - private ContentModifyingMethodInterceptor(String modifiedContent, - MockHttpServletResponse delegate) { - this.modifiedContent = modifiedContent; - this.delegate = delegate; - } - - @Override - public Object intercept(Object proxy, Method method, Object[] args, - MethodProxy methodProxy) throws IllegalAccessException, - InvocationTargetException { - if (this.getContentAsStringMethod.equals(method)) { - return this.modifiedContent; - } - if (this.getContentAsByteArray.equals(method)) { - return this.modifiedContent.getBytes(); - } - return method.invoke(this.delegate, args); - } - - private static Method findMethod(String methodName) { - return BridgeMethodResolver.findBridgedMethod(ReflectionUtils.findMethod( - MockHttpServletResponse.class, methodName)); - } - - } - -} diff --git a/spring-restdocs/src/main/java/org/springframework/restdocs/response/HeaderRemovingResponsePostProcessor.java b/spring-restdocs/src/main/java/org/springframework/restdocs/response/HeaderRemovingResponsePostProcessor.java deleted file mode 100644 index 53da363e..00000000 --- a/spring-restdocs/src/main/java/org/springframework/restdocs/response/HeaderRemovingResponsePostProcessor.java +++ /dev/null @@ -1,134 +0,0 @@ -/* - * Copyright 2014-2015 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.response; - -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.HashSet; -import java.util.List; -import java.util.Set; - -import org.springframework.cglib.proxy.Enhancer; -import org.springframework.cglib.proxy.MethodInterceptor; -import org.springframework.cglib.proxy.MethodProxy; -import org.springframework.core.BridgeMethodResolver; -import org.springframework.mock.web.MockHttpServletResponse; -import org.springframework.util.ReflectionUtils; - -/** - * A {@link ResponsePostProcessor} that removes headers from the response - * - * @author Andy Wilkinson - */ -class HeaderRemovingResponsePostProcessor implements ResponsePostProcessor { - - private final Set headersToRemove; - - HeaderRemovingResponsePostProcessor(String... headersToRemove) { - this.headersToRemove = new HashSet<>(Arrays.asList(headersToRemove)); - } - - @Override - public MockHttpServletResponse postProcess(final MockHttpServletResponse response) { - Enhancer enhancer = new Enhancer(); - enhancer.setSuperclass(MockHttpServletResponse.class); - enhancer.setCallback(new HeaderHidingMethodInterceptor(this.headersToRemove, - response)); - - return (MockHttpServletResponse) enhancer.create(); - } - - private static final class HeaderHidingMethodInterceptor implements MethodInterceptor { - - private final MockHttpServletResponse response; - - private final List interceptedMethods = Arrays.asList( - findHeaderMethod("containsHeader", String.class), - findHeaderMethod("getHeader", String.class), - findHeaderMethod("getHeaderValue", String.class), - findHeaderMethod("getHeaders", String.class), - findHeaderMethod("getHeaderValues", String.class)); - - private final Method getHeaderNamesMethod = findHeaderMethod("getHeaderNames"); - - private final Set hiddenHeaders; - - private HeaderHidingMethodInterceptor(Set hiddenHeaders, - MockHttpServletResponse response) { - this.hiddenHeaders = hiddenHeaders; - this.response = response; - } - - @Override - public Object intercept(Object proxy, Method method, Object[] args, - MethodProxy methodProxy) throws IllegalAccessException, - InvocationTargetException { - if (this.getHeaderNamesMethod.equals(method)) { - List headerNames = new ArrayList<>(); - for (String candidate : this.response.getHeaderNames()) { - if (!isHiddenHeader(candidate)) { - headerNames.add(candidate); - } - } - return headerNames; - } - if (this.interceptedMethods.contains(method) && isHiddenHeader(args)) { - if (method.getReturnType().equals(boolean.class)) { - return false; - } - else if (Collection.class.isAssignableFrom(method.getReturnType())) { - return Collections.emptyList(); - } - else { - return null; - } - } - - return method.invoke(this.response, args); - } - - private boolean isHiddenHeader(Object[] args) { - if (args.length == 1 && args[0] instanceof String) { - return isHiddenHeader((String) args[0]); - } - return false; - } - - private boolean isHiddenHeader(String headerName) { - for (String hiddenHeader : this.hiddenHeaders) { - if (hiddenHeader.equalsIgnoreCase(headerName)) { - return true; - } - } - return false; - } - - private static Method findHeaderMethod(String methodName, Class... args) { - Method candidate = ReflectionUtils.findMethod(MockHttpServletResponse.class, - methodName, args); - if (candidate.isBridge()) { - return BridgeMethodResolver.findBridgedMethod(candidate); - } - return candidate; - } - } - -} diff --git a/spring-restdocs/src/main/java/org/springframework/restdocs/response/ResponsePostProcessors.java b/spring-restdocs/src/main/java/org/springframework/restdocs/response/ResponsePostProcessors.java deleted file mode 100644 index 6c1f2295..00000000 --- a/spring-restdocs/src/main/java/org/springframework/restdocs/response/ResponsePostProcessors.java +++ /dev/null @@ -1,91 +0,0 @@ -/* - * Copyright 2014-2015 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.response; - -import java.util.regex.Pattern; - -/** - * Static factory methods for accessing various {@link ResponsePostProcessor - * ResponsePostProcessors}. - * - * @author Andy Wilkinson - * @author Dewet Diener - */ -public abstract class ResponsePostProcessors { - - private ResponsePostProcessors() { - - } - - /** - * Returns a {@link ResponsePostProcessor} that will pretty print the content of the - * response. - * - * @return the response post-processor - */ - public static ResponsePostProcessor prettyPrintContent() { - return new PrettyPrintingResponsePostProcessor(); - } - - /** - * Returns a {@link ResponsePostProcessor} that will remove the headers with the given - * {@code headerNames} from the response. - * - * @param headerNames the name of the headers to remove - * @return the response post-processor - */ - public static ResponsePostProcessor removeHeaders(String... headerNames) { - return new HeaderRemovingResponsePostProcessor(headerNames); - } - - /** - * Returns a {@link ResponsePostProcessor} that will update the content of the - * response to mask any links that it contains. Each link is masked my replacing its - * {@code href} with {@code ...}. - * - * @return the response post-processor - */ - public static ResponsePostProcessor maskLinks() { - return new LinkMaskingResponsePostProcessor(); - } - - /** - * Returns a {@link ResponsePostProcessor} that will update the content of the - * response to mask any links that it contains. Each link is masked my replacing its - * {@code href} with the given {@code mask}. - * - * @param mask the mask to apply - * @return the response post-processor - */ - public static ResponsePostProcessor maskLinksWith(String mask) { - return new LinkMaskingResponsePostProcessor(mask); - } - - /** - * Returns a {@link ResponsePostProcessor} that will update the content of the - * response by replacing any occurrences of the given {@code pattern} with the given - * {@code replacement}. - * - * @param pattern the pattern to match - * @param replacement the replacement to apply - * @return the response post-processor - */ - public static ResponsePostProcessor replacePattern(Pattern pattern, String replacement) { - return new PatternReplacingResponsePostProcessor(pattern, replacement); - } - -} diff --git a/spring-restdocs/src/test/java/org/springframework/restdocs/ResponseModifierTests.java b/spring-restdocs/src/test/java/org/springframework/restdocs/ResponseModifierTests.java deleted file mode 100644 index 63a3862c..00000000 --- a/spring-restdocs/src/test/java/org/springframework/restdocs/ResponseModifierTests.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright 2014-2015 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; - -import static org.hamcrest.CoreMatchers.equalTo; -import static org.hamcrest.CoreMatchers.is; -import static org.junit.Assert.assertThat; -import static org.mockito.BDDMockito.given; -import static org.mockito.Mockito.mock; -import static org.springframework.restdocs.test.StubMvcResult.result; - -import org.junit.Test; -import org.springframework.mock.web.MockHttpServletResponse; -import org.springframework.restdocs.ResponseModifier.ResponseModifyingRestDocumentationResultHandler; -import org.springframework.restdocs.response.ResponsePostProcessor; - -/** - * Tests for {@link ResponseModifier} - * - * @author Andy Wilkinson - * - */ -public class ResponseModifierTests { - - @Test - public void postProcessorsAreApplied() throws Exception { - ResponsePostProcessor first = mock(ResponsePostProcessor.class); - ResponsePostProcessor second = mock(ResponsePostProcessor.class); - - MockHttpServletResponse original = new MockHttpServletResponse(); - MockHttpServletResponse afterFirst = new MockHttpServletResponse(); - MockHttpServletResponse afterSecond = new MockHttpServletResponse(); - - given(first.postProcess(original)).willReturn(afterFirst); - given(second.postProcess(afterFirst)).willReturn(afterSecond); - - RestDocumentationResultHandler resultHandler = new ResponseModifier(first, second) - .andDocument("test"); - assertThat( - afterSecond, - is(equalTo(((ResponseModifyingRestDocumentationResultHandler) resultHandler) - .postProcessResponse(result(original)).getResponse()))); - } - -} diff --git a/spring-restdocs/src/test/java/org/springframework/restdocs/RestDocumentationIntegrationTests.java b/spring-restdocs/src/test/java/org/springframework/restdocs/RestDocumentationIntegrationTests.java index 194d0bd7..bd7c08bf 100644 --- a/spring-restdocs/src/test/java/org/springframework/restdocs/RestDocumentationIntegrationTests.java +++ b/spring-restdocs/src/test/java/org/springframework/restdocs/RestDocumentationIntegrationTests.java @@ -21,23 +21,25 @@ import static org.hamcrest.CoreMatchers.is; import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; import static org.springframework.restdocs.RestDocumentation.document; -import static org.springframework.restdocs.RestDocumentation.modifyResponseTo; import static org.springframework.restdocs.RestDocumentationRequestBuilders.get; import static org.springframework.restdocs.curl.CurlDocumentation.curlRequest; import static org.springframework.restdocs.hypermedia.HypermediaDocumentation.linkWithRel; import static org.springframework.restdocs.hypermedia.HypermediaDocumentation.links; +import static org.springframework.restdocs.operation.preprocess.Preprocessors.maskLinks; +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.operation.preprocess.Preprocessors.replacePattern; import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath; import static org.springframework.restdocs.payload.PayloadDocumentation.requestFields; import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields; import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName; import static org.springframework.restdocs.request.RequestDocumentation.pathParameters; import static org.springframework.restdocs.request.RequestDocumentation.requestParameters; -import static org.springframework.restdocs.response.ResponsePostProcessors.maskLinks; -import static org.springframework.restdocs.response.ResponsePostProcessors.prettyPrintContent; -import static org.springframework.restdocs.response.ResponsePostProcessors.removeHeaders; -import static org.springframework.restdocs.response.ResponsePostProcessors.replacePattern; import static org.springframework.restdocs.snippet.Attributes.attributes; import static org.springframework.restdocs.snippet.Attributes.key; +import static org.springframework.restdocs.test.SnippetMatchers.httpRequest; import static org.springframework.restdocs.test.SnippetMatchers.httpResponse; import static org.springframework.restdocs.test.SnippetMatchers.snippet; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @@ -71,6 +73,7 @@ import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.setup.MockMvcBuilders; import org.springframework.util.FileSystemUtils; import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.context.WebApplicationContext; import org.springframework.web.servlet.config.annotation.EnableWebMvc; @@ -236,15 +239,62 @@ public class RestDocumentationIntegrationTests { } @Test - public void postProcessedResponse() throws Exception { + public void preprocessedRequest() throws Exception { MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.context) .apply(new RestDocumentationConfigurer()).build(); - mockMvc.perform(get("/").accept(MediaType.APPLICATION_JSON)) - .andExpect(status().isOk()).andDo(document("original")); + Pattern pattern = Pattern.compile("(\"alpha\")"); + + mockMvc.perform( + get("/").header("a", "alpha").header("b", "bravo") + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON).content("{\"a\":\"alpha\"}")) + .andExpect(status().isOk()) + .andDo(document("original-request")) + .andDo(document( + "preprocessed-request", + preprocessRequest(prettyPrint(), removeHeaders("a"), + replacePattern(pattern, "\"<>\"")))); assertThat( - new File("build/generated-snippets/original/http-response.adoc"), + new File("build/generated-snippets/original-request/http-request.adoc"), + is(snippet().withContents( + httpRequest(RequestMethod.GET, "/").header("Host", "localhost") + .header("a", "alpha").header("b", "bravo") + .header("Content-Type", "application/json") + .header("Accept", MediaType.APPLICATION_JSON_VALUE) + .header("Content-Length", "13") + .content("{\"a\":\"alpha\"}")))); + assertThat( + new File( + "build/generated-snippets/preprocessed-request/http-request.adoc"), + is(snippet().withContents( + httpRequest(RequestMethod.GET, "/").header("Host", "localhost") + .header("b", "bravo") + .header("Content-Type", "application/json") + .header("Accept", MediaType.APPLICATION_JSON_VALUE) + .header("Content-Length", "22") + .content(String.format("{%n \"a\" : \"<>\"%n}"))))); + } + + @Test + public void preprocessedResponse() throws Exception { + MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.context) + .apply(new RestDocumentationConfigurer()).build(); + + Pattern pattern = Pattern.compile("(\"alpha\")"); + + mockMvc.perform(get("/").accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andDo(document("original-response")) + .andDo(document( + "preprocessed-response", + preprocessResponse(prettyPrint(), maskLinks(), + removeHeaders("a"), + replacePattern(pattern, "\"<>\"")))); + + assertThat( + new File("build/generated-snippets/original-response/http-response.adoc"), is(snippet().withContents( httpResponse(HttpStatus.OK) .header("a", "alpha") @@ -252,16 +302,9 @@ public class RestDocumentationIntegrationTests { .content( "{\"a\":\"alpha\",\"links\":[{\"rel\":\"rel\"," + "\"href\":\"href\"}]}")))); - - Pattern pattern = Pattern.compile("(\"alpha\")"); - mockMvc.perform(get("/").accept(MediaType.APPLICATION_JSON)) - .andExpect(status().isOk()) - .andDo(modifyResponseTo(prettyPrintContent(), removeHeaders("a"), - replacePattern(pattern, "\"<>\""), maskLinks()) - .andDocument("post-processed")); - assertThat( - new File("build/generated-snippets/post-processed/http-response.adoc"), + new File( + "build/generated-snippets/preprocessed-response/http-response.adoc"), is(snippet().withContents( httpResponse(HttpStatus.OK).header("Content-Type", "application/json").content( diff --git a/spring-restdocs/src/test/java/org/springframework/restdocs/operation/preprocess/ContentModifyingOperationPreprocessorTests.java b/spring-restdocs/src/test/java/org/springframework/restdocs/operation/preprocess/ContentModifyingOperationPreprocessorTests.java new file mode 100644 index 00000000..0a31fd8f --- /dev/null +++ b/spring-restdocs/src/test/java/org/springframework/restdocs/operation/preprocess/ContentModifyingOperationPreprocessorTests.java @@ -0,0 +1,79 @@ +package org.springframework.restdocs.operation.preprocess; + +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertThat; + +import java.net.URI; +import java.util.Collections; + +import org.junit.Test; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.HttpStatus; +import org.springframework.restdocs.operation.OperationRequest; +import org.springframework.restdocs.operation.OperationRequestPart; +import org.springframework.restdocs.operation.OperationResponse; +import org.springframework.restdocs.operation.Parameters; +import org.springframework.restdocs.operation.StandardOperationRequest; +import org.springframework.restdocs.operation.StandardOperationResponse; + +/** + * Tests for {@link ContentModifyingOperationPreprocessor} + * + * @author Andy Wilkinson + * + */ +public class ContentModifyingOperationPreprocessorTests { + + private final ContentModifyingOperationPreprocessor preprocessor = new ContentModifyingOperationPreprocessor( + new ContentModifier() { + + @Override + public byte[] modifyContent(byte[] originalContent) { + return "modified".getBytes(); + } + + }); + + @Test + public void modifyRequestContent() { + StandardOperationRequest request = new StandardOperationRequest( + URI.create("http://localhost"), HttpMethod.GET, "content".getBytes(), + new HttpHeaders(), new Parameters(), + Collections. emptyList()); + OperationRequest preprocessed = this.preprocessor.preprocess(request); + assertThat(preprocessed.getContent(), is(equalTo("modified".getBytes()))); + } + + @Test + public void modifyResponseContent() { + StandardOperationResponse response = new StandardOperationResponse(HttpStatus.OK, + new HttpHeaders(), "content".getBytes()); + OperationResponse preprocessed = this.preprocessor.preprocess(response); + assertThat(preprocessed.getContent(), is(equalTo("modified".getBytes()))); + } + + @Test + public void unknownContentLengthIsUnchanged() { + StandardOperationRequest request = new StandardOperationRequest( + URI.create("http://localhost"), HttpMethod.GET, "content".getBytes(), + new HttpHeaders(), new Parameters(), + Collections. emptyList()); + OperationRequest preprocessed = this.preprocessor.preprocess(request); + assertThat(preprocessed.getHeaders().getContentLength(), is(equalTo(-1L))); + } + + @Test + public void contentLengthIsUpdated() { + HttpHeaders httpHeaders = new HttpHeaders(); + httpHeaders.setContentLength(7); + StandardOperationRequest request = new StandardOperationRequest( + URI.create("http://localhost"), HttpMethod.GET, "content".getBytes(), + httpHeaders, new Parameters(), + Collections. emptyList()); + OperationRequest preprocessed = this.preprocessor.preprocess(request); + assertThat(preprocessed.getHeaders().getContentLength(), is(equalTo(8L))); + } + +} diff --git a/spring-restdocs/src/test/java/org/springframework/restdocs/operation/preprocess/HeaderRemovingOperationPreprocessorTests.java b/spring-restdocs/src/test/java/org/springframework/restdocs/operation/preprocess/HeaderRemovingOperationPreprocessorTests.java new file mode 100644 index 00000000..c90f4afd --- /dev/null +++ b/spring-restdocs/src/test/java/org/springframework/restdocs/operation/preprocess/HeaderRemovingOperationPreprocessorTests.java @@ -0,0 +1,62 @@ +package org.springframework.restdocs.operation.preprocess; + +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.Matchers.hasEntry; +import static org.junit.Assert.assertThat; + +import java.net.URI; +import java.util.Arrays; +import java.util.Collections; + +import org.junit.Test; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.HttpStatus; +import org.springframework.restdocs.operation.OperationRequest; +import org.springframework.restdocs.operation.OperationRequestPart; +import org.springframework.restdocs.operation.OperationResponse; +import org.springframework.restdocs.operation.Parameters; +import org.springframework.restdocs.operation.StandardOperationRequest; +import org.springframework.restdocs.operation.StandardOperationResponse; + +/** + * Tests for {@link HeaderRemovingOperationPreprocessorTests} + * + * @author Andy Wilkinson + * + */ +public class HeaderRemovingOperationPreprocessorTests { + + private final HeaderRemovingOperationPreprocessor preprocessor = new HeaderRemovingOperationPreprocessor( + "b"); + + @Test + public void modifyRequestHeaders() { + StandardOperationRequest request = new StandardOperationRequest( + URI.create("http://localhost"), HttpMethod.GET, new byte[0], + getHttpHeaders(), new Parameters(), + Collections. emptyList()); + OperationRequest preprocessed = this.preprocessor.preprocess(request); + assertThat(preprocessed.getHeaders().size(), is(equalTo(1))); + assertThat(preprocessed.getHeaders(), hasEntry("a", Arrays.asList("alpha"))); + } + + @Test + public void modifyResponseHeaders() { + StandardOperationResponse response = new StandardOperationResponse(HttpStatus.OK, + getHttpHeaders(), new byte[0]); + OperationResponse preprocessed = this.preprocessor.preprocess(response); + assertThat(preprocessed.getHeaders().size(), is(equalTo(1))); + assertThat(preprocessed.getHeaders(), hasEntry("a", Arrays.asList("alpha"))); + } + + private HttpHeaders getHttpHeaders() { + HttpHeaders httpHeaders = new HttpHeaders(); + httpHeaders.add("a", "alpha"); + httpHeaders.add("b", "bravo"); + httpHeaders.add("b", "banana"); + return httpHeaders; + } + +} diff --git a/spring-restdocs/src/test/java/org/springframework/restdocs/response/LinkMaskingResponsePostProcessorTests.java b/spring-restdocs/src/test/java/org/springframework/restdocs/operation/preprocess/LinkMaskingContentModifierTests.java similarity index 65% rename from spring-restdocs/src/test/java/org/springframework/restdocs/response/LinkMaskingResponsePostProcessorTests.java rename to spring-restdocs/src/test/java/org/springframework/restdocs/operation/preprocess/LinkMaskingContentModifierTests.java index ddc66d45..2763d646 100644 --- a/spring-restdocs/src/test/java/org/springframework/restdocs/response/LinkMaskingResponsePostProcessorTests.java +++ b/spring-restdocs/src/test/java/org/springframework/restdocs/operation/preprocess/LinkMaskingContentModifierTests.java @@ -1,20 +1,4 @@ -/* - * Copyright 2014-2015 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.response; +package org.springframework.restdocs.operation.preprocess; import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.CoreMatchers.is; @@ -34,9 +18,15 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializationFeature; -public class LinkMaskingResponsePostProcessorTests { +/** + * Tests for {@link LinkMaskingContentModifier} + * + * @author Andy Wilkinson + * + */ +public class LinkMaskingContentModifierTests { - private final LinkMaskingResponsePostProcessor postProcessor = new LinkMaskingResponsePostProcessor(); + private final ContentModifier contentModifier = new LinkMaskingContentModifier(); private final Link[] links = new Link[] { new Link("a", "alpha"), new Link("b", "bravo") }; @@ -46,28 +36,28 @@ public class LinkMaskingResponsePostProcessorTests { @Test public void halLinksAreMasked() throws Exception { - assertThat(this.postProcessor.modifyContent(halPayloadWithLinks(this.links)), + assertThat(this.contentModifier.modifyContent(halPayloadWithLinks(this.links)), is(equalTo(halPayloadWithLinks(this.maskedLinks)))); } @Test public void formattedHalLinksAreMasked() throws Exception { assertThat( - this.postProcessor + this.contentModifier .modifyContent(formattedHalPayloadWithLinks(this.links)), is(equalTo(formattedHalPayloadWithLinks(this.maskedLinks)))); } @Test public void atomLinksAreMasked() throws Exception { - assertThat(this.postProcessor.modifyContent(atomPayloadWithLinks(this.links)), + assertThat(this.contentModifier.modifyContent(atomPayloadWithLinks(this.links)), is(equalTo(atomPayloadWithLinks(this.maskedLinks)))); } @Test public void formattedAtomLinksAreMasked() throws Exception { assertThat( - this.postProcessor + this.contentModifier .modifyContent(formattedAtomPayloadWithLinks(this.links)), is(equalTo(formattedAtomPayloadWithLinks(this.maskedLinks)))); } @@ -75,20 +65,20 @@ public class LinkMaskingResponsePostProcessorTests { @Test public void maskCanBeCustomized() throws Exception { assertThat( - new LinkMaskingResponsePostProcessor("custom") + new LinkMaskingContentModifier("custom") .modifyContent(formattedAtomPayloadWithLinks(this.links)), is(equalTo(formattedAtomPayloadWithLinks(new Link("a", "custom"), new Link("b", "custom"))))); } - private String atomPayloadWithLinks(Link... links) throws JsonProcessingException { - return new ObjectMapper().writeValueAsString(createAtomPayload(links)); + private byte[] atomPayloadWithLinks(Link... links) throws JsonProcessingException { + return new ObjectMapper().writeValueAsBytes(createAtomPayload(links)); } - private String formattedAtomPayloadWithLinks(Link... links) + private byte[] formattedAtomPayloadWithLinks(Link... links) throws JsonProcessingException { return new ObjectMapper().configure(SerializationFeature.INDENT_OUTPUT, true) - .writeValueAsString(createAtomPayload(links)); + .writeValueAsBytes(createAtomPayload(links)); } private AtomPayload createAtomPayload(Link... links) { @@ -97,14 +87,14 @@ public class LinkMaskingResponsePostProcessorTests { return payload; } - private String halPayloadWithLinks(Link... links) throws JsonProcessingException { - return new ObjectMapper().writeValueAsString(createHalPayload(links)); + private byte[] halPayloadWithLinks(Link... links) throws JsonProcessingException { + return new ObjectMapper().writeValueAsBytes(createHalPayload(links)); } - private String formattedHalPayloadWithLinks(Link... links) + private byte[] formattedHalPayloadWithLinks(Link... links) throws JsonProcessingException { return new ObjectMapper().configure(SerializationFeature.INDENT_OUTPUT, true) - .writeValueAsString(createHalPayload(links)); + .writeValueAsBytes(createHalPayload(links)); } private HalPayload createHalPayload(Link... links) { diff --git a/spring-restdocs/src/test/java/org/springframework/restdocs/operation/preprocess/PatternReplacingContentModifierTests.java b/spring-restdocs/src/test/java/org/springframework/restdocs/operation/preprocess/PatternReplacingContentModifierTests.java new file mode 100644 index 00000000..f419296d --- /dev/null +++ b/spring-restdocs/src/test/java/org/springframework/restdocs/operation/preprocess/PatternReplacingContentModifierTests.java @@ -0,0 +1,31 @@ +package org.springframework.restdocs.operation.preprocess; + +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertThat; + +import java.util.regex.Pattern; + +import org.junit.Test; + +/** + * Tests for {@link PatternReplacingContentModifier} + * + * @author Andy Wilkinson + * + */ +public class PatternReplacingContentModifierTests { + + @Test + public void patternsAreReplaced() throws Exception { + Pattern pattern = Pattern.compile( + "([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})", + Pattern.CASE_INSENSITIVE); + PatternReplacingContentModifier contentModifier = new PatternReplacingContentModifier( + pattern, "<>"); + assertThat( + contentModifier.modifyContent("{\"id\" : \"CA761232-ED42-11CE-BACD-00AA0057B223\"}" + .getBytes()), is(equalTo("{\"id\" : \"<>\"}".getBytes()))); + } + +} diff --git a/spring-restdocs/src/test/java/org/springframework/restdocs/response/PrettyPrintingResponsePostProcessorTests.java b/spring-restdocs/src/test/java/org/springframework/restdocs/operation/preprocess/PrettyPrintingContentModifierTests.java similarity index 57% rename from spring-restdocs/src/test/java/org/springframework/restdocs/response/PrettyPrintingResponsePostProcessorTests.java rename to spring-restdocs/src/test/java/org/springframework/restdocs/operation/preprocess/PrettyPrintingContentModifierTests.java index 5a5b4577..8e6aa197 100644 --- a/spring-restdocs/src/test/java/org/springframework/restdocs/response/PrettyPrintingResponsePostProcessorTests.java +++ b/spring-restdocs/src/test/java/org/springframework/restdocs/operation/preprocess/PrettyPrintingContentModifierTests.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.restdocs.response; +package org.springframework.restdocs.operation.preprocess; import static org.hamcrest.CoreMatchers.equalTo; import static org.junit.Assert.assertThat; @@ -22,38 +22,40 @@ import static org.junit.Assert.assertThat; import org.junit.Test; /** - * Tests for {@link PrettyPrintingResponsePostProcessor} + * Tests for {@link PrettyPrintingContentModifier} * * @author Andy Wilkinson * */ -public class PrettyPrintingResponsePostProcessorTests { +public class PrettyPrintingContentModifierTests { @Test public void prettyPrintJson() throws Exception { - assertThat(new PrettyPrintingResponsePostProcessor().modifyContent("{\"a\":5}"), - equalTo(String.format("{%n \"a\" : 5%n}"))); + assertThat( + new PrettyPrintingContentModifier().modifyContent("{\"a\":5}".getBytes()), + equalTo(String.format("{%n \"a\" : 5%n}").getBytes())); } @Test public void prettyPrintXml() throws Exception { assertThat( - new PrettyPrintingResponsePostProcessor() - .modifyContent(""), - equalTo(String.format("%n" - + "%n %n%n"))); + new PrettyPrintingContentModifier().modifyContent("" + .getBytes()), equalTo(String.format( + "%n" + + "%n %n%n") + .getBytes())); } @Test public void empytContentIsHandledGracefully() throws Exception { - assertThat(new PrettyPrintingResponsePostProcessor().modifyContent(""), - equalTo("")); + assertThat(new PrettyPrintingContentModifier().modifyContent("".getBytes()), + equalTo("".getBytes())); } @Test public void nonJsonAndNonXmlContentIsHandledGracefully() throws Exception { String content = "abcdefg"; - assertThat(new PrettyPrintingResponsePostProcessor().modifyContent(content), - equalTo(content)); + assertThat(new PrettyPrintingContentModifier().modifyContent(content.getBytes()), + equalTo(content.getBytes())); } } diff --git a/spring-restdocs/src/test/java/org/springframework/restdocs/response/ContentModifyingResponsePostProcessorTests.java b/spring-restdocs/src/test/java/org/springframework/restdocs/response/ContentModifyingResponsePostProcessorTests.java deleted file mode 100644 index 09487a3d..00000000 --- a/spring-restdocs/src/test/java/org/springframework/restdocs/response/ContentModifyingResponsePostProcessorTests.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright 2014-2015 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.response; - -import static org.hamcrest.CoreMatchers.equalTo; -import static org.hamcrest.CoreMatchers.is; -import static org.junit.Assert.assertThat; - -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.ExpectedException; -import org.springframework.mock.web.MockHttpServletResponse; - -public class ContentModifyingResponsePostProcessorTests { - - private final MockHttpServletResponse original = new MockHttpServletResponse(); - - private final ContentModifyingReponsePostProcessor postProcessor = new TestContentModifyingResponsePostProcessor(); - - @Rule - public ExpectedException thrown = ExpectedException.none(); - - @Test - public void contentCanBeModified() throws Exception { - MockHttpServletResponse modified = this.postProcessor.postProcess(this.original); - assertThat(modified.getContentAsString(), is(equalTo("modified"))); - assertThat(modified.getContentAsByteArray(), is(equalTo("modified".getBytes()))); - } - - @Test - public void nonContentMethodsAreDelegated() throws Exception { - this.original.addHeader("a", "alpha"); - MockHttpServletResponse modified = this.postProcessor.postProcess(this.original); - assertThat(modified.getHeader("a"), is(equalTo("alpha"))); - } - - private static final class TestContentModifyingResponsePostProcessor extends - ContentModifyingReponsePostProcessor { - - @Override - protected String modifyContent(String originalContent) throws Exception { - return "modified"; - } - - } - -} diff --git a/spring-restdocs/src/test/java/org/springframework/restdocs/response/HeaderRemovingResponsePostProcessorTests.java b/spring-restdocs/src/test/java/org/springframework/restdocs/response/HeaderRemovingResponsePostProcessorTests.java deleted file mode 100644 index 1891eadc..00000000 --- a/spring-restdocs/src/test/java/org/springframework/restdocs/response/HeaderRemovingResponsePostProcessorTests.java +++ /dev/null @@ -1,91 +0,0 @@ -/* - * Copyright 2014-2015 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.response; - -import static org.hamcrest.CoreMatchers.is; -import static org.hamcrest.CoreMatchers.nullValue; -import static org.hamcrest.Matchers.contains; -import static org.hamcrest.Matchers.empty; -import static org.junit.Assert.assertThat; - -import org.junit.Before; -import org.junit.Test; -import org.springframework.mock.web.MockHttpServletResponse; - -/** - * Tests for {@link HeaderRemovingResponsePostProcessor}. - * - * @author Andy Wilkinson - * - */ -public class HeaderRemovingResponsePostProcessorTests { - - private final MockHttpServletResponse response = new MockHttpServletResponse(); - - @Before - public void configureResponse() { - this.response.addHeader("a", "alpha"); - this.response.addHeader("b", "bravo"); - } - - @Test - public void containsHeaderHonoursRemovedHeaders() { - MockHttpServletResponse response = removeHeaders("a"); - assertThat(response.containsHeader("a"), is(false)); - assertThat(response.containsHeader("b"), is(true)); - } - - @Test - public void getHeaderNamesHonoursRemovedHeaders() { - MockHttpServletResponse response = removeHeaders("a"); - assertThat(response.getHeaderNames(), contains("b")); - } - - @Test - public void getHeaderHonoursRemovedHeaders() { - MockHttpServletResponse response = removeHeaders("a"); - assertThat(response.getHeader("a"), is(nullValue())); - assertThat(response.getHeader("b"), is("bravo")); - } - - @Test - public void getHeadersHonoursRemovedHeaders() { - MockHttpServletResponse response = removeHeaders("a"); - assertThat(response.getHeaders("a"), is(empty())); - assertThat(response.getHeaders("b"), contains("bravo")); - } - - @Test - public void getHeaderValueHonoursRemovedHeaders() { - MockHttpServletResponse response = removeHeaders("a"); - assertThat(response.getHeaderValue("a"), is(nullValue())); - assertThat(response.getHeaderValue("b"), is((Object) "bravo")); - } - - @Test - public void getHeaderValuesHonoursRemovedHeaders() { - MockHttpServletResponse response = removeHeaders("a"); - assertThat(response.getHeaderValues("a"), is(empty())); - assertThat(response.getHeaderValues("b"), contains((Object) "bravo")); - } - - private MockHttpServletResponse removeHeaders(String... headerNames) { - return new HeaderRemovingResponsePostProcessor(headerNames) - .postProcess(this.response); - } - -} diff --git a/spring-restdocs/src/test/java/org/springframework/restdocs/response/PatternReplacingResponsePostProcessorTests.java b/spring-restdocs/src/test/java/org/springframework/restdocs/response/PatternReplacingResponsePostProcessorTests.java deleted file mode 100644 index 1074ea35..00000000 --- a/spring-restdocs/src/test/java/org/springframework/restdocs/response/PatternReplacingResponsePostProcessorTests.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright 2014-2015 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.response; - -import static org.hamcrest.CoreMatchers.equalTo; -import static org.hamcrest.CoreMatchers.is; -import static org.junit.Assert.assertThat; - -import java.util.regex.Pattern; - -import org.junit.Test; - -/** - * Tests for {@link PatternReplacingResponsePostProcessor}. - * - * @author Dewet Diener - */ -public class PatternReplacingResponsePostProcessorTests { - - @Test - public void patternsAreReplaced() throws Exception { - Pattern pattern = Pattern.compile( - "([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})", - Pattern.CASE_INSENSITIVE); - PatternReplacingResponsePostProcessor postProcessor = new PatternReplacingResponsePostProcessor( - pattern, "<>"); - assertThat( - postProcessor - .modifyContent("{\"id\" : \"CA761232-ED42-11CE-BACD-00AA0057B223\"}"), - is(equalTo("{\"id\" : \"<>\"}"))); - } - -}