From 768a43a61ea326afa7249ff58f9c7e01d7fc5fbc Mon Sep 17 00:00:00 2001 From: Jihoon Cha Date: Mon, 7 Mar 2022 18:54:34 +0900 Subject: [PATCH 1/2] Provide a preprocessor for modifying request and response headers See gh-584 --- .../customizing-requests-and-responses.adoc | 7 + .../HeaderRemovingOperationPreprocessor.java | 2 + ...HeadersModifyingOperationPreprocessor.java | 227 ++++++++++++++++++ .../operation/preprocess/Preprocessors.java | 14 ++ ...rsModifyingOperationPreprocessorTests.java | 155 ++++++++++++ 5 files changed, 405 insertions(+) create mode 100644 spring-restdocs-core/src/main/java/org/springframework/restdocs/operation/preprocess/HeadersModifyingOperationPreprocessor.java create mode 100644 spring-restdocs-core/src/test/java/org/springframework/restdocs/operation/preprocess/HeadersModifyingOperationPreprocessorTests.java diff --git a/docs/src/docs/asciidoc/customizing-requests-and-responses.adoc b/docs/src/docs/asciidoc/customizing-requests-and-responses.adoc index ec8f2932..06776da4 100644 --- a/docs/src/docs/asciidoc/customizing-requests-and-responses.adoc +++ b/docs/src/docs/asciidoc/customizing-requests-and-responses.adoc @@ -125,6 +125,13 @@ Any occurrences that match a regular expression are replaced. +[[customizing-requests-and-responses-preprocessors-modify-headers]] +==== Modifying Headers + +You can use `modifyHeaders` on `Preprocessors` to add, set, and remove request or response headers. + + + [[customizing-requests-and-responses-preprocessors-modify-request-parameters]] ==== Modifying Request Parameters diff --git a/spring-restdocs-core/src/main/java/org/springframework/restdocs/operation/preprocess/HeaderRemovingOperationPreprocessor.java b/spring-restdocs-core/src/main/java/org/springframework/restdocs/operation/preprocess/HeaderRemovingOperationPreprocessor.java index 01710e99..f8bf8cc9 100644 --- a/spring-restdocs-core/src/main/java/org/springframework/restdocs/operation/preprocess/HeaderRemovingOperationPreprocessor.java +++ b/spring-restdocs-core/src/main/java/org/springframework/restdocs/operation/preprocess/HeaderRemovingOperationPreprocessor.java @@ -31,7 +31,9 @@ import org.springframework.restdocs.operation.OperationResponseFactory; * against the headers found * * @author Andy Wilkinson + * @deprecated Use {@link HeadersModifyingOperationPreprocessor} instead */ +@Deprecated class HeaderRemovingOperationPreprocessor implements OperationPreprocessor { private final OperationRequestFactory requestFactory = new OperationRequestFactory(); diff --git a/spring-restdocs-core/src/main/java/org/springframework/restdocs/operation/preprocess/HeadersModifyingOperationPreprocessor.java b/spring-restdocs-core/src/main/java/org/springframework/restdocs/operation/preprocess/HeadersModifyingOperationPreprocessor.java new file mode 100644 index 00000000..fb002dac --- /dev/null +++ b/spring-restdocs-core/src/main/java/org/springframework/restdocs/operation/preprocess/HeadersModifyingOperationPreprocessor.java @@ -0,0 +1,227 @@ +/* + * Copyright 2014-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.restdocs.operation.preprocess; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.springframework.http.HttpHeaders; +import org.springframework.restdocs.operation.OperationRequest; +import org.springframework.restdocs.operation.OperationRequestFactory; +import org.springframework.restdocs.operation.OperationResponse; +import org.springframework.restdocs.operation.OperationResponseFactory; +import org.springframework.util.Assert; + +/** + * An {@link OperationPreprocessor} that can be used to modify a request's + * {@link OperationRequest#getHeaders()} by adding, setting, and removing headers. + * + * @author Jihoon Cha + */ +public class HeadersModifyingOperationPreprocessor implements OperationPreprocessor { + + private final OperationRequestFactory requestFactory = new OperationRequestFactory(); + + private final OperationResponseFactory responseFactory = new OperationResponseFactory(); + + private final List modifications = new ArrayList<>(); + + @Override + public OperationRequest preprocess(OperationRequest request) { + HttpHeaders headers = copyHttpHeaders(request.getHeaders()); + for (Modification modification : this.modifications) { + modification.applyTo(headers); + } + return this.requestFactory.createFrom(request, headers); + } + + @Override + public OperationResponse preprocess(OperationResponse response) { + HttpHeaders headers = copyHttpHeaders(response.getHeaders()); + for (Modification modification : this.modifications) { + modification.applyTo(headers); + } + return this.responseFactory.createFrom(response, headers); + } + + private HttpHeaders copyHttpHeaders(HttpHeaders headers) { + HttpHeaders copy = new HttpHeaders(); + for (String name : headers.keySet()) { + List values = headers.get(name); + if (values == null) { + continue; + } + copy.put(name, new ArrayList<>(values)); + } + return copy; + } + + /** + * Adds a header with the given {@code name} and {@code value}. + * @param name the name + * @param value the value + * @return {@code this} + */ + public HeadersModifyingOperationPreprocessor add(String name, String value) { + this.modifications.add(new AddHeaderModification(name, value)); + return this; + } + + /** + * Sets the header with the given {@code name} to have the given {@code values}. + * @param name the name + * @param values the values + * @return {@code this} + */ + public HeadersModifyingOperationPreprocessor set(String name, String... values) { + Assert.notEmpty(values, "At least one value must be provided"); + this.modifications.add(new SetHeaderModification(name, Arrays.asList(values))); + return this; + } + + /** + * Removes the header with the given {@code name}. + * @param name the name of the parameter + * @return {@code this} + */ + public HeadersModifyingOperationPreprocessor remove(String name) { + this.modifications.add(new RemoveHeaderModification(name)); + return this; + } + + /** + * Removes the given {@code value} from the header with the given {@code name}. + * @param name the name + * @param value the value + * @return {@code this} + */ + public HeadersModifyingOperationPreprocessor remove(String name, String value) { + this.modifications.add(new RemoveValueHeaderModification(name, value)); + return this; + } + + /** + * Remove headers that match the given {@code namePattern} regular expression. + * @param namePattern the name pattern + * @return {@code this} + * @see Matcher#matches() + */ + public HeadersModifyingOperationPreprocessor remove(Pattern namePattern) { + this.modifications.add(new RemoveHeadersByNamePatternModification(namePattern)); + return this; + } + + private interface Modification { + + void applyTo(HttpHeaders headers); + + } + + private static final class AddHeaderModification implements Modification { + + private final String name; + + private final String value; + + private AddHeaderModification(String name, String value) { + this.name = name; + this.value = value; + } + + @Override + public void applyTo(HttpHeaders headers) { + headers.add(this.name, this.value); + } + + } + + private static final class SetHeaderModification implements Modification { + + private final String name; + + private final List values; + + private SetHeaderModification(String name, List values) { + this.name = name; + this.values = values; + } + + @Override + public void applyTo(HttpHeaders headers) { + headers.put(this.name, this.values); + } + + } + + private static final class RemoveHeaderModification implements Modification { + + private final String name; + + private RemoveHeaderModification(String name) { + this.name = name; + } + + @Override + public void applyTo(HttpHeaders headers) { + headers.remove(this.name); + } + + } + + private static final class RemoveValueHeaderModification implements Modification { + + private final String name; + + private final String value; + + private RemoveValueHeaderModification(String name, String value) { + this.name = name; + this.value = value; + } + + @Override + public void applyTo(HttpHeaders headers) { + List values = headers.get(this.name); + if (values != null) { + values.remove(this.value); + if (values.isEmpty()) { + headers.remove(this.name); + } + } + } + + } + + private static final class RemoveHeadersByNamePatternModification implements Modification { + + private final Pattern namePattern; + + private RemoveHeadersByNamePatternModification(Pattern namePattern) { + this.namePattern = namePattern; + } + + @Override + public void applyTo(HttpHeaders headers) { + headers.keySet().removeIf((name) -> this.namePattern.matcher(name).matches()); + } + + } + +} diff --git a/spring-restdocs-core/src/main/java/org/springframework/restdocs/operation/preprocess/Preprocessors.java b/spring-restdocs-core/src/main/java/org/springframework/restdocs/operation/preprocess/Preprocessors.java index 6be9107c..4fb1647e 100644 --- a/spring-restdocs-core/src/main/java/org/springframework/restdocs/operation/preprocess/Preprocessors.java +++ b/spring-restdocs-core/src/main/java/org/springframework/restdocs/operation/preprocess/Preprocessors.java @@ -31,6 +31,7 @@ import org.springframework.restdocs.operation.OperationResponse; * * @author Andy Wilkinson * @author Roland Huss + * @author Jihoon Cha */ public final class Preprocessors { @@ -73,8 +74,10 @@ public final class Preprocessors { * {@code headersToRemove}. * @param headerNames the header names * @return the preprocessor + * @deprecated Use {@link #modifyHeaders()} instead * @see String#equals(Object) */ + @Deprecated public static OperationPreprocessor removeHeaders(String... headerNames) { return new HeaderRemovingOperationPreprocessor(new ExactMatchHeaderFilter(headerNames)); } @@ -85,8 +88,10 @@ public final class Preprocessors { * {@code headerNamePatterns} regular expressions. * @param headerNamePatterns the header name patterns * @return the preprocessor + * @deprecated Use {@link #modifyHeaders()} instead * @see java.util.regex.Matcher#matches() */ + @Deprecated public static OperationPreprocessor removeMatchingHeaders(String... headerNamePatterns) { return new HeaderRemovingOperationPreprocessor(new PatternMatchHeaderFilter(headerNamePatterns)); } @@ -132,6 +137,15 @@ public final class Preprocessors { return new ParametersModifyingOperationPreprocessor(); } + /** + * Returns a {@code HeadersModifyingOperationPreprocessor} that can then be configured + * to modify the headers of the request. + * @return the preprocessor + */ + public static HeadersModifyingOperationPreprocessor modifyHeaders() { + return new HeadersModifyingOperationPreprocessor(); + } + /** * Returns a {@code UriModifyingOperationPreprocessor} that will modify URIs in the * request or response by changing one or more of their host, scheme, and port. diff --git a/spring-restdocs-core/src/test/java/org/springframework/restdocs/operation/preprocess/HeadersModifyingOperationPreprocessorTests.java b/spring-restdocs-core/src/test/java/org/springframework/restdocs/operation/preprocess/HeadersModifyingOperationPreprocessorTests.java new file mode 100644 index 00000000..78129895 --- /dev/null +++ b/spring-restdocs-core/src/test/java/org/springframework/restdocs/operation/preprocess/HeadersModifyingOperationPreprocessorTests.java @@ -0,0 +1,155 @@ +/* + * Copyright 2014-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.restdocs.operation.preprocess; + +import java.net.URI; +import java.util.Arrays; +import java.util.Collections; +import java.util.regex.Pattern; + +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.OperationRequestFactory; +import org.springframework.restdocs.operation.OperationResponse; +import org.springframework.restdocs.operation.OperationResponseFactory; +import org.springframework.restdocs.operation.Parameters; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link HeadersModifyingOperationPreprocessor}. + * + * @author Jihoon Cha + */ +public class HeadersModifyingOperationPreprocessorTests { + + private final HeadersModifyingOperationPreprocessor preprocessor = new HeadersModifyingOperationPreprocessor(); + + @Test + public void addNewHeader() { + HttpHeaders headers = new HttpHeaders(); + OperationPreprocessor preprocessor = this.preprocessor.add("a", "alpha"); + assertThat(preprocessor.preprocess(createRequest(headers)).getHeaders()).containsEntry("a", + Arrays.asList("alpha")); + assertThat(preprocessor.preprocess(createResponse(headers)).getHeaders()).containsEntry("a", + Arrays.asList("alpha")); + } + + @Test + public void addValueToExistingHeader() { + HttpHeaders headers = new HttpHeaders(); + headers.add("a", "apple"); + OperationPreprocessor preprocessor = this.preprocessor.add("a", "alpha"); + assertThat(preprocessor.preprocess(createRequest(headers)).getHeaders()).containsEntry("a", + Arrays.asList("apple", "alpha")); + assertThat(preprocessor.preprocess(createResponse(headers)).getHeaders()).containsEntry("a", + Arrays.asList("apple", "alpha")); + } + + @Test + public void setNewHeader() { + HttpHeaders headers = new HttpHeaders(); + OperationPreprocessor preprocessor = this.preprocessor.set("a", "alpha", "avocado"); + assertThat(preprocessor.preprocess(createRequest(headers)).getHeaders()).containsEntry("a", + Arrays.asList("alpha", "avocado")); + assertThat(preprocessor.preprocess(createResponse(headers)).getHeaders()).containsEntry("a", + Arrays.asList("alpha", "avocado")); + } + + @Test + public void setExistingHeader() { + HttpHeaders headers = new HttpHeaders(); + headers.add("a", "apple"); + OperationPreprocessor preprocessor = this.preprocessor.set("a", "alpha", "avocado"); + assertThat(preprocessor.preprocess(createRequest(headers)).getHeaders()).containsEntry("a", + Arrays.asList("alpha", "avocado")); + assertThat(preprocessor.preprocess(createResponse(headers)).getHeaders()).containsEntry("a", + Arrays.asList("alpha", "avocado")); + } + + @Test + public void removeNonExistentHeader() { + HttpHeaders headers = new HttpHeaders(); + OperationPreprocessor preprocessor = this.preprocessor.remove("a"); + assertThat(preprocessor.preprocess(createRequest(headers)).getHeaders()).doesNotContainKey("a"); + assertThat(preprocessor.preprocess(createResponse(headers)).getHeaders()).doesNotContainKey("a"); + } + + @Test + public void removeHeader() { + HttpHeaders headers = new HttpHeaders(); + headers.add("a", "apple"); + OperationPreprocessor preprocessor = this.preprocessor.remove("a"); + assertThat(preprocessor.preprocess(createRequest(headers)).getHeaders()).doesNotContainKey("a"); + assertThat(preprocessor.preprocess(createResponse(headers)).getHeaders()).doesNotContainKey("a"); + } + + @Test + public void removeHeaderValueForNonExistentHeader() { + HttpHeaders headers = new HttpHeaders(); + OperationPreprocessor preprocessor = this.preprocessor.remove("a", "apple"); + assertThat(preprocessor.preprocess(createRequest(headers)).getHeaders()).doesNotContainKey("a"); + assertThat(preprocessor.preprocess(createResponse(headers)).getHeaders()).doesNotContainKey("a"); + } + + @Test + public void removeHeaderValueWithMultipleValues() { + HttpHeaders headers = new HttpHeaders(); + headers.add("a", "apple"); + headers.add("a", "alpha"); + OperationPreprocessor preprocessor = this.preprocessor.remove("a", "apple"); + assertThat(preprocessor.preprocess(createRequest(headers)).getHeaders()).containsEntry("a", + Arrays.asList("alpha")); + assertThat(preprocessor.preprocess(createResponse(headers)).getHeaders()).containsEntry("a", + Arrays.asList("alpha")); + } + + @Test + public void removeHeaderValueWithSingleValueRemovesEntryEntirely() { + HttpHeaders headers = new HttpHeaders(); + headers.add("a", "apple"); + OperationPreprocessor preprocessor = this.preprocessor.remove("a", "apple"); + assertThat(preprocessor.preprocess(createRequest(headers)).getHeaders()).doesNotContainKey("a"); + assertThat(preprocessor.preprocess(createResponse(headers)).getHeaders()).doesNotContainKey("a"); + } + + @Test + public void removeHeadersByNamePattern() { + HttpHeaders headers = new HttpHeaders(); + headers.add("apple", "apple"); + headers.add("alpha", "alpha"); + headers.add("avocado", "avocado"); + headers.add("bravo", "bravo"); + OperationPreprocessor preprocessor = this.preprocessor.remove(Pattern.compile("^a.*")); + assertThat(preprocessor.preprocess(createRequest(headers)).getHeaders().size()).isEqualTo(2); + assertThat(preprocessor.preprocess(createResponse(headers)).getHeaders().size()).isEqualTo(1); + } + + private OperationRequest createRequest(HttpHeaders headers) { + return new OperationRequestFactory().create(URI.create("http://localhost:8080"), HttpMethod.GET, new byte[0], + headers, new Parameters(), Collections.emptyList()); + } + + private OperationResponse createResponse(HttpHeaders headers) { + return new OperationResponseFactory().create(HttpStatus.OK.value(), headers, new byte[0]); + } + +} From 36418f47d7a06664496edbc3ff5aa41c86f4e5e2 Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Thu, 12 May 2022 14:27:49 +0100 Subject: [PATCH 2/2] Polish "Provide a preprocessor for modifying request and response headers" See gh-584 --- .../customizing-requests-and-responses.adoc | 15 +- .../HeaderRemovingOperationPreprocessor.java | 69 ---------- ...HeadersModifyingOperationPreprocessor.java | 37 ++--- .../operation/preprocess/Preprocessors.java | 19 ++- ...derRemovingOperationPreprocessorTests.java | 104 -------------- ...rsModifyingOperationPreprocessorTests.java | 128 ++++++++++-------- 6 files changed, 102 insertions(+), 270 deletions(-) delete mode 100644 spring-restdocs-core/src/main/java/org/springframework/restdocs/operation/preprocess/HeaderRemovingOperationPreprocessor.java delete mode 100644 spring-restdocs-core/src/test/java/org/springframework/restdocs/operation/preprocess/HeaderRemovingOperationPreprocessorTests.java diff --git a/docs/src/docs/asciidoc/customizing-requests-and-responses.adoc b/docs/src/docs/asciidoc/customizing-requests-and-responses.adoc index 06776da4..f267612f 100644 --- a/docs/src/docs/asciidoc/customizing-requests-and-responses.adoc +++ b/docs/src/docs/asciidoc/customizing-requests-and-responses.adoc @@ -108,12 +108,10 @@ You can also specify a different replacement if you wish. -[[customizing-requests-and-responses-preprocessors-remove-headers]] -==== Removing Headers +[[customizing-requests-and-responses-preprocessors-modify-headers]] +==== Modifying Headers -`removeHeaders` on `Preprocessors` removes any headers from the request or response where the name is equal to any of the given header names. - -`removeMatchingHeaders` on `Preprocessors` removes any headers from the request or response where the name matches any of the given regular expression patterns. +You can use `modifyHeaders` on `Preprocessors` to add, set, and remove request or response headers. @@ -125,13 +123,6 @@ Any occurrences that match a regular expression are replaced. -[[customizing-requests-and-responses-preprocessors-modify-headers]] -==== Modifying Headers - -You can use `modifyHeaders` on `Preprocessors` to add, set, and remove request or response headers. - - - [[customizing-requests-and-responses-preprocessors-modify-request-parameters]] ==== Modifying Request Parameters diff --git a/spring-restdocs-core/src/main/java/org/springframework/restdocs/operation/preprocess/HeaderRemovingOperationPreprocessor.java b/spring-restdocs-core/src/main/java/org/springframework/restdocs/operation/preprocess/HeaderRemovingOperationPreprocessor.java deleted file mode 100644 index f8bf8cc9..00000000 --- a/spring-restdocs-core/src/main/java/org/springframework/restdocs/operation/preprocess/HeaderRemovingOperationPreprocessor.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright 2014-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.restdocs.operation.preprocess; - -import java.util.List; -import java.util.Map.Entry; - -import org.springframework.http.HttpHeaders; -import org.springframework.restdocs.operation.OperationRequest; -import org.springframework.restdocs.operation.OperationRequestFactory; -import org.springframework.restdocs.operation.OperationResponse; -import org.springframework.restdocs.operation.OperationResponseFactory; - -/** - * An {@link OperationPreprocessor} that removes headers. The headers to remove are - * provided as constructor arguments and can be either plain string or patterns to match - * against the headers found - * - * @author Andy Wilkinson - * @deprecated Use {@link HeadersModifyingOperationPreprocessor} instead - */ -@Deprecated -class HeaderRemovingOperationPreprocessor implements OperationPreprocessor { - - private final OperationRequestFactory requestFactory = new OperationRequestFactory(); - - private final OperationResponseFactory responseFactory = new OperationResponseFactory(); - - private final HeaderFilter headerFilter; - - HeaderRemovingOperationPreprocessor(HeaderFilter headerFilter) { - this.headerFilter = headerFilter; - } - - @Override - public OperationResponse preprocess(OperationResponse response) { - return this.responseFactory.createFrom(response, removeHeaders(response.getHeaders())); - } - - @Override - public OperationRequest preprocess(OperationRequest request) { - return this.requestFactory.createFrom(request, removeHeaders(request.getHeaders())); - } - - private HttpHeaders removeHeaders(HttpHeaders originalHeaders) { - HttpHeaders processedHeaders = new HttpHeaders(); - for (Entry> header : originalHeaders.entrySet()) { - if (!this.headerFilter.excludeHeader(header.getKey())) { - processedHeaders.put(header.getKey(), header.getValue()); - } - } - return processedHeaders; - } - -} diff --git a/spring-restdocs-core/src/main/java/org/springframework/restdocs/operation/preprocess/HeadersModifyingOperationPreprocessor.java b/spring-restdocs-core/src/main/java/org/springframework/restdocs/operation/preprocess/HeadersModifyingOperationPreprocessor.java index fb002dac..72078847 100644 --- a/spring-restdocs-core/src/main/java/org/springframework/restdocs/operation/preprocess/HeadersModifyingOperationPreprocessor.java +++ b/spring-restdocs-core/src/main/java/org/springframework/restdocs/operation/preprocess/HeadersModifyingOperationPreprocessor.java @@ -30,10 +30,12 @@ import org.springframework.restdocs.operation.OperationResponseFactory; import org.springframework.util.Assert; /** - * An {@link OperationPreprocessor} that can be used to modify a request's - * {@link OperationRequest#getHeaders()} by adding, setting, and removing headers. + * An {@link OperationPreprocessor} that modifies a request or response by adding, + * setting, or removing headers. * * @author Jihoon Cha + * @author Andy Wilkinson + * @since 3.0.0 */ public class HeadersModifyingOperationPreprocessor implements OperationPreprocessor { @@ -45,32 +47,21 @@ public class HeadersModifyingOperationPreprocessor implements OperationPreproces @Override public OperationRequest preprocess(OperationRequest request) { - HttpHeaders headers = copyHttpHeaders(request.getHeaders()); - for (Modification modification : this.modifications) { - modification.applyTo(headers); - } - return this.requestFactory.createFrom(request, headers); + return this.requestFactory.createFrom(request, preprocess(request.getHeaders())); } @Override public OperationResponse preprocess(OperationResponse response) { - HttpHeaders headers = copyHttpHeaders(response.getHeaders()); - for (Modification modification : this.modifications) { - modification.applyTo(headers); - } - return this.responseFactory.createFrom(response, headers); + return this.responseFactory.createFrom(response, preprocess(response.getHeaders())); } - private HttpHeaders copyHttpHeaders(HttpHeaders headers) { - HttpHeaders copy = new HttpHeaders(); - for (String name : headers.keySet()) { - List values = headers.get(name); - if (values == null) { - continue; - } - copy.put(name, new ArrayList<>(values)); + private HttpHeaders preprocess(HttpHeaders headers) { + HttpHeaders modifiedHeaders = new HttpHeaders(); + modifiedHeaders.putAll(headers); + for (Modification modification : this.modifications) { + modification.applyTo(modifiedHeaders); } - return copy; + return modifiedHeaders; } /** @@ -123,8 +114,8 @@ public class HeadersModifyingOperationPreprocessor implements OperationPreproces * @return {@code this} * @see Matcher#matches() */ - public HeadersModifyingOperationPreprocessor remove(Pattern namePattern) { - this.modifications.add(new RemoveHeadersByNamePatternModification(namePattern)); + public HeadersModifyingOperationPreprocessor removeMatching(String namePattern) { + this.modifications.add(new RemoveHeadersByNamePatternModification(Pattern.compile(namePattern))); return this; } diff --git a/spring-restdocs-core/src/main/java/org/springframework/restdocs/operation/preprocess/Preprocessors.java b/spring-restdocs-core/src/main/java/org/springframework/restdocs/operation/preprocess/Preprocessors.java index 4fb1647e..a70b2169 100644 --- a/spring-restdocs-core/src/main/java/org/springframework/restdocs/operation/preprocess/Preprocessors.java +++ b/spring-restdocs-core/src/main/java/org/springframework/restdocs/operation/preprocess/Preprocessors.java @@ -74,12 +74,17 @@ public final class Preprocessors { * {@code headersToRemove}. * @param headerNames the header names * @return the preprocessor - * @deprecated Use {@link #modifyHeaders()} instead + * @deprecated since 3.0.0 in favor of {@link #modifyHeaders()} and + * {@link HeadersModifyingOperationPreprocessor#remove(String)} * @see String#equals(Object) */ @Deprecated public static OperationPreprocessor removeHeaders(String... headerNames) { - return new HeaderRemovingOperationPreprocessor(new ExactMatchHeaderFilter(headerNames)); + HeadersModifyingOperationPreprocessor preprocessor = new HeadersModifyingOperationPreprocessor(); + for (String headerName : headerNames) { + preprocessor.remove(headerName); + } + return preprocessor; } /** @@ -88,12 +93,17 @@ public final class Preprocessors { * {@code headerNamePatterns} regular expressions. * @param headerNamePatterns the header name patterns * @return the preprocessor - * @deprecated Use {@link #modifyHeaders()} instead + * @deprecated since 3.0.0 in favor of {@link #modifyHeaders()} and + * {@link HeadersModifyingOperationPreprocessor#removeMatching(String)} * @see java.util.regex.Matcher#matches() */ @Deprecated public static OperationPreprocessor removeMatchingHeaders(String... headerNamePatterns) { - return new HeaderRemovingOperationPreprocessor(new PatternMatchHeaderFilter(headerNamePatterns)); + HeadersModifyingOperationPreprocessor preprocessor = new HeadersModifyingOperationPreprocessor(); + for (String headerNamePattern : headerNamePatterns) { + preprocessor.removeMatching(headerNamePattern); + } + return preprocessor; } /** @@ -141,6 +151,7 @@ public final class Preprocessors { * Returns a {@code HeadersModifyingOperationPreprocessor} that can then be configured * to modify the headers of the request. * @return the preprocessor + * @since 3.0.0 */ public static HeadersModifyingOperationPreprocessor modifyHeaders() { return new HeadersModifyingOperationPreprocessor(); diff --git a/spring-restdocs-core/src/test/java/org/springframework/restdocs/operation/preprocess/HeaderRemovingOperationPreprocessorTests.java b/spring-restdocs-core/src/test/java/org/springframework/restdocs/operation/preprocess/HeaderRemovingOperationPreprocessorTests.java deleted file mode 100644 index 87f5e8c0..00000000 --- a/spring-restdocs-core/src/test/java/org/springframework/restdocs/operation/preprocess/HeaderRemovingOperationPreprocessorTests.java +++ /dev/null @@ -1,104 +0,0 @@ -/* - * Copyright 2014-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.restdocs.operation.preprocess; - -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.OperationRequestFactory; -import org.springframework.restdocs.operation.OperationRequestPart; -import org.springframework.restdocs.operation.OperationResponse; -import org.springframework.restdocs.operation.OperationResponseFactory; -import org.springframework.restdocs.operation.Parameters; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * Tests for {@link HeaderRemovingOperationPreprocessorTests}. - * - * @author Andy Wilkinson - * @author Roland Huss - */ -public class HeaderRemovingOperationPreprocessorTests { - - private final OperationRequestFactory requestFactory = new OperationRequestFactory(); - - private final OperationResponseFactory responseFactory = new OperationResponseFactory(); - - private final HeaderRemovingOperationPreprocessor preprocessor = new HeaderRemovingOperationPreprocessor( - new ExactMatchHeaderFilter("b")); - - @Test - public void modifyRequestHeaders() { - OperationRequest request = this.requestFactory.create(URI.create("http://localhost"), HttpMethod.GET, - new byte[0], getHttpHeaders(), new Parameters(), Collections.emptyList()); - OperationRequest preprocessed = this.preprocessor.preprocess(request); - assertThat(preprocessed.getHeaders().size()).isEqualTo(2); - assertThat(preprocessed.getHeaders()).containsEntry("a", Arrays.asList("alpha")); - assertThat(preprocessed.getHeaders()).containsEntry("Host", Arrays.asList("localhost")); - } - - @Test - public void modifyResponseHeaders() { - OperationResponse response = createResponse(); - OperationResponse preprocessed = this.preprocessor.preprocess(response); - assertThat(preprocessed.getHeaders().size()).isEqualTo(1); - assertThat(preprocessed.getHeaders()).containsEntry("a", Arrays.asList("alpha")); - } - - @Test - public void modifyWithPattern() { - OperationResponse response = createResponse("content-length", "1234"); - HeaderRemovingOperationPreprocessor processor = new HeaderRemovingOperationPreprocessor( - new PatternMatchHeaderFilter("co.*le(.)gth]")); - OperationResponse preprocessed = processor.preprocess(response); - assertThat(preprocessed.getHeaders().size()).isEqualTo(2); - assertThat(preprocessed.getHeaders()).containsEntry("a", Arrays.asList("alpha")); - assertThat(preprocessed.getHeaders()).containsEntry("b", Arrays.asList("bravo", "banana")); - } - - @Test - public void removeAllHeaders() { - HeaderRemovingOperationPreprocessor processor = new HeaderRemovingOperationPreprocessor( - new PatternMatchHeaderFilter(".*")); - OperationResponse preprocessed = processor.preprocess(createResponse()); - assertThat(preprocessed.getHeaders().size()).isEqualTo(0); - } - - private OperationResponse createResponse(String... extraHeaders) { - return this.responseFactory.create(HttpStatus.OK.value(), getHttpHeaders(extraHeaders), new byte[0]); - } - - private HttpHeaders getHttpHeaders(String... extraHeaders) { - HttpHeaders httpHeaders = new HttpHeaders(); - httpHeaders.add("a", "alpha"); - httpHeaders.add("b", "bravo"); - httpHeaders.add("b", "banana"); - for (int i = 0; i < extraHeaders.length; i += 2) { - httpHeaders.add(extraHeaders[i], extraHeaders[i + 1]); - } - return httpHeaders; - } - -} diff --git a/spring-restdocs-core/src/test/java/org/springframework/restdocs/operation/preprocess/HeadersModifyingOperationPreprocessorTests.java b/spring-restdocs-core/src/test/java/org/springframework/restdocs/operation/preprocess/HeadersModifyingOperationPreprocessorTests.java index 78129895..ad55d141 100644 --- a/spring-restdocs-core/src/test/java/org/springframework/restdocs/operation/preprocess/HeadersModifyingOperationPreprocessorTests.java +++ b/spring-restdocs-core/src/test/java/org/springframework/restdocs/operation/preprocess/HeadersModifyingOperationPreprocessorTests.java @@ -19,7 +19,8 @@ package org.springframework.restdocs.operation.preprocess; import java.net.URI; import java.util.Arrays; import java.util.Collections; -import java.util.regex.Pattern; +import java.util.List; +import java.util.function.Consumer; import org.junit.Test; @@ -38,6 +39,7 @@ import static org.assertj.core.api.Assertions.assertThat; * Tests for {@link HeadersModifyingOperationPreprocessor}. * * @author Jihoon Cha + * @author Andy Wilkinson */ public class HeadersModifyingOperationPreprocessorTests { @@ -45,110 +47,120 @@ public class HeadersModifyingOperationPreprocessorTests { @Test public void addNewHeader() { - HttpHeaders headers = new HttpHeaders(); - OperationPreprocessor preprocessor = this.preprocessor.add("a", "alpha"); - assertThat(preprocessor.preprocess(createRequest(headers)).getHeaders()).containsEntry("a", + this.preprocessor.add("a", "alpha"); + assertThat(this.preprocessor.preprocess(createRequest()).getHeaders()).containsEntry("a", Arrays.asList("alpha")); - assertThat(preprocessor.preprocess(createResponse(headers)).getHeaders()).containsEntry("a", + assertThat(this.preprocessor.preprocess(createResponse()).getHeaders()).containsEntry("a", Arrays.asList("alpha")); } @Test public void addValueToExistingHeader() { - HttpHeaders headers = new HttpHeaders(); - headers.add("a", "apple"); - OperationPreprocessor preprocessor = this.preprocessor.add("a", "alpha"); - assertThat(preprocessor.preprocess(createRequest(headers)).getHeaders()).containsEntry("a", - Arrays.asList("apple", "alpha")); - assertThat(preprocessor.preprocess(createResponse(headers)).getHeaders()).containsEntry("a", - Arrays.asList("apple", "alpha")); + this.preprocessor.add("a", "alpha"); + assertThat(this.preprocessor.preprocess(createRequest((headers) -> headers.add("a", "apple"))).getHeaders()) + .containsEntry("a", Arrays.asList("apple", "alpha")); + assertThat(this.preprocessor.preprocess(createResponse((headers) -> headers.add("a", "apple"))).getHeaders()) + .containsEntry("a", Arrays.asList("apple", "alpha")); } @Test public void setNewHeader() { - HttpHeaders headers = new HttpHeaders(); - OperationPreprocessor preprocessor = this.preprocessor.set("a", "alpha", "avocado"); - assertThat(preprocessor.preprocess(createRequest(headers)).getHeaders()).containsEntry("a", + this.preprocessor.set("a", "alpha", "avocado"); + assertThat(this.preprocessor.preprocess(createRequest()).getHeaders()).containsEntry("a", Arrays.asList("alpha", "avocado")); - assertThat(preprocessor.preprocess(createResponse(headers)).getHeaders()).containsEntry("a", + assertThat(this.preprocessor.preprocess(createResponse()).getHeaders()).containsEntry("a", Arrays.asList("alpha", "avocado")); } @Test public void setExistingHeader() { - HttpHeaders headers = new HttpHeaders(); - headers.add("a", "apple"); - OperationPreprocessor preprocessor = this.preprocessor.set("a", "alpha", "avocado"); - assertThat(preprocessor.preprocess(createRequest(headers)).getHeaders()).containsEntry("a", - Arrays.asList("alpha", "avocado")); - assertThat(preprocessor.preprocess(createResponse(headers)).getHeaders()).containsEntry("a", - Arrays.asList("alpha", "avocado")); + this.preprocessor.set("a", "alpha", "avocado"); + assertThat(this.preprocessor.preprocess(createRequest((headers) -> headers.add("a", "apple"))).getHeaders()) + .containsEntry("a", Arrays.asList("alpha", "avocado")); + assertThat(this.preprocessor.preprocess(createResponse((headers) -> headers.add("a", "apple"))).getHeaders()) + .containsEntry("a", Arrays.asList("alpha", "avocado")); } @Test public void removeNonExistentHeader() { - HttpHeaders headers = new HttpHeaders(); - OperationPreprocessor preprocessor = this.preprocessor.remove("a"); - assertThat(preprocessor.preprocess(createRequest(headers)).getHeaders()).doesNotContainKey("a"); - assertThat(preprocessor.preprocess(createResponse(headers)).getHeaders()).doesNotContainKey("a"); + this.preprocessor.remove("a"); + assertThat(this.preprocessor.preprocess(createRequest()).getHeaders()).doesNotContainKey("a"); + assertThat(this.preprocessor.preprocess(createResponse()).getHeaders()).doesNotContainKey("a"); } @Test public void removeHeader() { - HttpHeaders headers = new HttpHeaders(); - headers.add("a", "apple"); OperationPreprocessor preprocessor = this.preprocessor.remove("a"); - assertThat(preprocessor.preprocess(createRequest(headers)).getHeaders()).doesNotContainKey("a"); - assertThat(preprocessor.preprocess(createResponse(headers)).getHeaders()).doesNotContainKey("a"); + assertThat(preprocessor.preprocess(createRequest((headers) -> headers.add("a", "apple"))).getHeaders()) + .doesNotContainKey("a"); + assertThat(preprocessor.preprocess(createResponse((headers) -> headers.add("a", "apple"))).getHeaders()) + .doesNotContainKey("a"); } @Test public void removeHeaderValueForNonExistentHeader() { - HttpHeaders headers = new HttpHeaders(); - OperationPreprocessor preprocessor = this.preprocessor.remove("a", "apple"); - assertThat(preprocessor.preprocess(createRequest(headers)).getHeaders()).doesNotContainKey("a"); - assertThat(preprocessor.preprocess(createResponse(headers)).getHeaders()).doesNotContainKey("a"); + this.preprocessor.remove("a", "apple"); + assertThat(this.preprocessor.preprocess(createRequest()).getHeaders()).doesNotContainKey("a"); + assertThat(this.preprocessor.preprocess(createResponse()).getHeaders()).doesNotContainKey("a"); } @Test public void removeHeaderValueWithMultipleValues() { - HttpHeaders headers = new HttpHeaders(); - headers.add("a", "apple"); - headers.add("a", "alpha"); - OperationPreprocessor preprocessor = this.preprocessor.remove("a", "apple"); - assertThat(preprocessor.preprocess(createRequest(headers)).getHeaders()).containsEntry("a", - Arrays.asList("alpha")); - assertThat(preprocessor.preprocess(createResponse(headers)).getHeaders()).containsEntry("a", - Arrays.asList("alpha")); + this.preprocessor.remove("a", "apple"); + assertThat(this.preprocessor + .preprocess(createRequest((headers) -> headers.addAll("a", List.of("apple", "alpha")))).getHeaders()) + .containsEntry("a", Arrays.asList("alpha")); + assertThat(this.preprocessor + .preprocess(createResponse((headers) -> headers.addAll("a", List.of("apple", "alpha")))).getHeaders()) + .containsEntry("a", Arrays.asList("alpha")); } @Test public void removeHeaderValueWithSingleValueRemovesEntryEntirely() { - HttpHeaders headers = new HttpHeaders(); - headers.add("a", "apple"); - OperationPreprocessor preprocessor = this.preprocessor.remove("a", "apple"); - assertThat(preprocessor.preprocess(createRequest(headers)).getHeaders()).doesNotContainKey("a"); - assertThat(preprocessor.preprocess(createResponse(headers)).getHeaders()).doesNotContainKey("a"); + this.preprocessor.remove("a", "apple"); + assertThat(this.preprocessor.preprocess(createRequest((headers) -> headers.add("a", "apple"))).getHeaders()) + .doesNotContainKey("a"); + assertThat(this.preprocessor.preprocess(createResponse((headers) -> headers.add("a", "apple"))).getHeaders()) + .doesNotContainKey("a"); } @Test public void removeHeadersByNamePattern() { - HttpHeaders headers = new HttpHeaders(); - headers.add("apple", "apple"); - headers.add("alpha", "alpha"); - headers.add("avocado", "avocado"); - headers.add("bravo", "bravo"); - OperationPreprocessor preprocessor = this.preprocessor.remove(Pattern.compile("^a.*")); - assertThat(preprocessor.preprocess(createRequest(headers)).getHeaders().size()).isEqualTo(2); - assertThat(preprocessor.preprocess(createResponse(headers)).getHeaders().size()).isEqualTo(1); + Consumer headersCustomizer = (headers) -> { + headers.add("apple", "apple"); + headers.add("alpha", "alpha"); + headers.add("avocado", "avocado"); + headers.add("bravo", "bravo"); + }; + this.preprocessor.removeMatching("^a.*"); + assertThat(this.preprocessor.preprocess(createRequest(headersCustomizer)).getHeaders()).containsOnlyKeys("Host", + "bravo"); + assertThat(this.preprocessor.preprocess(createResponse(headersCustomizer)).getHeaders()) + .containsOnlyKeys("bravo"); } - private OperationRequest createRequest(HttpHeaders headers) { + private OperationRequest createRequest() { + return createRequest(null); + } + + private OperationRequest createRequest(Consumer headersCustomizer) { + HttpHeaders headers = new HttpHeaders(); + if (headersCustomizer != null) { + headersCustomizer.accept(headers); + } return new OperationRequestFactory().create(URI.create("http://localhost:8080"), HttpMethod.GET, new byte[0], headers, new Parameters(), Collections.emptyList()); } - private OperationResponse createResponse(HttpHeaders headers) { + private OperationResponse createResponse() { + return createResponse(null); + } + + private OperationResponse createResponse(Consumer headersCustomizer) { + HttpHeaders headers = new HttpHeaders(); + if (headersCustomizer != null) { + headersCustomizer.accept(headers); + } return new OperationResponseFactory().create(HttpStatus.OK.value(), headers, new byte[0]); }