Isolate and reduce Spring Test dependencies

This commit splits Spring REST Docs into two projects –
spring-restdocs-core and spring-restdocs-mockmvc.

spring-restdocs-core contains the vast majority of the code but does not
depend on a specific test framework other than JUnit. The use of a
Spring Test TestExecutionListener has been replaced with a JUnit test
rule. The rule is declared once per test class and configured with
the output directory to which the generated snippets should be written.
This simplifies the implementation as thread local storage is no longer
required to transfer information about the test that’s running into
Spring REST Docs. Instead, this transfer is now handled by the new test
rule. It has also simplified the configuration as it’s no longer
necessary for users to provide a system property that configures the
output directory.

spring-restdocs-mockmvc contains code that’s specific to using Spring
REST Docs with Spring MVC Test’s MockMvc. This is currently the only
testing framework that’s supported, but it paves the way for adding
support for additional frameworks. REST Assured is one that users seem
particularly interested in (see gh-80 and gh-102).

Closes gh-107
This commit is contained in:
Andy Wilkinson
2015-09-02 16:20:34 +01:00
parent 9d8bbf0558
commit 2b2b6fcd25
207 changed files with 1396 additions and 1425 deletions

View File

@@ -0,0 +1,198 @@
/*
* 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.constraints;
import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.hasSize;
import static org.junit.Assert.assertThat;
import java.math.BigDecimal;
import java.util.Date;
import javax.validation.constraints.AssertFalse;
import javax.validation.constraints.AssertTrue;
import javax.validation.constraints.DecimalMax;
import javax.validation.constraints.DecimalMin;
import javax.validation.constraints.Digits;
import javax.validation.constraints.Future;
import javax.validation.constraints.Max;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Null;
import javax.validation.constraints.Past;
import javax.validation.constraints.Pattern;
import javax.validation.constraints.Size;
import org.junit.Test;
/**
* Tests for {@link ConstraintDescriptions}
*
* @author Andy Wilkinson
*/
public class ConstraintDescriptionsTests {
private final ConstraintDescriptions constraintDescriptions = new ConstraintDescriptions(
Constrained.class);
@Test
public void assertFalse() {
assertThat(this.constraintDescriptions.descriptionsForProperty("assertFalse"),
contains("Must be false"));
}
@Test
public void assertTrue() {
assertThat(this.constraintDescriptions.descriptionsForProperty("assertTrue"),
contains("Must be true"));
}
@Test
public void decimalMax() {
assertThat(this.constraintDescriptions.descriptionsForProperty("decimalMax"),
contains("Must be at most 9.875"));
}
@Test
public void decimalMin() {
assertThat(this.constraintDescriptions.descriptionsForProperty("decimalMin"),
contains("Must be at least 1.5"));
}
@Test
public void digits() {
assertThat(this.constraintDescriptions.descriptionsForProperty("digits"),
contains("Must have at most 2 integral digits and 5 fractional digits"));
}
@Test
public void future() {
assertThat(this.constraintDescriptions.descriptionsForProperty("future"),
contains("Must be in the future"));
}
@Test
public void max() {
assertThat(this.constraintDescriptions.descriptionsForProperty("max"),
contains("Must be at most 10"));
}
@Test
public void min() {
assertThat(this.constraintDescriptions.descriptionsForProperty("min"),
contains("Must be at least 5"));
}
@Test
public void notNull() {
assertThat(this.constraintDescriptions.descriptionsForProperty("notNull"),
contains("Must not be null"));
}
@Test
public void nul() {
assertThat(this.constraintDescriptions.descriptionsForProperty("nul"),
contains("Must be null"));
}
@Test
public void past() {
assertThat(this.constraintDescriptions.descriptionsForProperty("past"),
contains("Must be in the past"));
}
@Test
public void pattern() {
assertThat(this.constraintDescriptions.descriptionsForProperty("pattern"),
contains("Must match the regular expression '[A-Z][a-z]+'"));
}
@Test
public void size() {
assertThat(this.constraintDescriptions.descriptionsForProperty("size"),
contains("Size must be between 0 and 10 inclusive"));
}
@Test
public void sizeList() {
assertThat(
this.constraintDescriptions.descriptionsForProperty("sizeList"),
contains("Size must be between 1 and 4 inclusive",
"Size must be between 8 and 10 inclusive"));
}
@Test
public void unconstrained() {
assertThat(this.constraintDescriptions.descriptionsForProperty("unconstrained"),
hasSize(0));
}
@Test
public void nonExistentProperty() {
assertThat(this.constraintDescriptions.descriptionsForProperty("doesNotExist"),
hasSize(0));
}
private static class Constrained {
@AssertFalse
private boolean assertFalse;
@AssertTrue
private boolean assertTrue;
@DecimalMax("9.875")
private BigDecimal decimalMax;
@DecimalMin("1.5")
private BigDecimal decimalMin;
@Digits(fraction = 5, integer = 2)
private BigDecimal digits;
@Future
private Date future;
@NotNull
private String notNull;
@Max(10)
private int max;
@Min(5)
private int min;
@Null
private String nul;
@Past
private Date past;
@Pattern(regexp = "[A-Z][a-z]+")
private String pattern;
@Size(min = 0, max = 10)
private String size;
@Size.List({ @Size(min = 1, max = 4), @Size(min = 8, max = 10) })
private String sizeList;
@SuppressWarnings("unused")
private String unconstrained;
}
}

View File

@@ -0,0 +1,208 @@
/*
* 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.constraints;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;
import java.net.URL;
import java.util.Collections;
import java.util.HashMap;
import java.util.ListResourceBundle;
import java.util.Map;
import java.util.ResourceBundle;
import javax.validation.constraints.AssertFalse;
import javax.validation.constraints.AssertTrue;
import javax.validation.constraints.DecimalMax;
import javax.validation.constraints.DecimalMin;
import javax.validation.constraints.Digits;
import javax.validation.constraints.Future;
import javax.validation.constraints.Max;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Null;
import javax.validation.constraints.Past;
import javax.validation.constraints.Pattern;
import javax.validation.constraints.Size;
import org.junit.Test;
/**
* Tests for {@link ResourceBundleConstraintDescriptionResolver}
*
* @author Andy Wilkinson
*/
public class ResourceBundleConstraintDescriptionResolverTests {
private final ResourceBundleConstraintDescriptionResolver resolver = new ResourceBundleConstraintDescriptionResolver();
@Test
public void defaultMessageAssertFalse() {
String description = this.resolver.resolveDescription(new Constraint(
AssertFalse.class.getName(), Collections.<String, Object> emptyMap()));
assertThat(description, is(equalTo("Must be false")));
}
@Test
public void defaultMessageAssertTrue() {
String description = this.resolver.resolveDescription(new Constraint(
AssertTrue.class.getName(), Collections.<String, Object> emptyMap()));
assertThat(description, is(equalTo("Must be true")));
}
@Test
public void defaultMessageDecimalMax() {
Map<String, Object> configuration = new HashMap<>();
configuration.put("value", "9.875");
String description = this.resolver.resolveDescription(new Constraint(
DecimalMax.class.getName(), configuration));
assertThat(description, is(equalTo("Must be at most 9.875")));
}
@Test
public void defaultMessageDecimalMin() {
Map<String, Object> configuration = new HashMap<>();
configuration.put("value", "1.5");
String description = this.resolver.resolveDescription(new Constraint(
DecimalMin.class.getName(), configuration));
assertThat(description, is(equalTo("Must be at least 1.5")));
}
@Test
public void defaultMessageDigits() {
Map<String, Object> configuration = new HashMap<>();
configuration.put("integer", "2");
configuration.put("fraction", "5");
String description = this.resolver.resolveDescription(new Constraint(Digits.class
.getName(), configuration));
assertThat(description, is(equalTo("Must have at most 2 integral digits and 5 "
+ "fractional digits")));
}
@Test
public void defaultMessageFuture() {
String description = this.resolver.resolveDescription(new Constraint(Future.class
.getName(), Collections.<String, Object> emptyMap()));
assertThat(description, is(equalTo("Must be in the future")));
}
@Test
public void defaultMessageMax() {
Map<String, Object> configuration = new HashMap<>();
configuration.put("value", 10);
String description = this.resolver.resolveDescription(new Constraint(Max.class
.getName(), configuration));
assertThat(description, is(equalTo("Must be at most 10")));
}
@Test
public void defaultMessageMin() {
Map<String, Object> configuration = new HashMap<>();
configuration.put("value", 10);
String description = this.resolver.resolveDescription(new Constraint(Min.class
.getName(), configuration));
assertThat(description, is(equalTo("Must be at least 10")));
}
@Test
public void defaultMessageNotNull() {
String description = this.resolver.resolveDescription(new Constraint(
NotNull.class.getName(), Collections.<String, Object> emptyMap()));
assertThat(description, is(equalTo("Must not be null")));
}
@Test
public void defaultMessageNull() {
String description = this.resolver.resolveDescription(new Constraint(Null.class
.getName(), Collections.<String, Object> emptyMap()));
assertThat(description, is(equalTo("Must be null")));
}
@Test
public void defaultMessagePast() {
String description = this.resolver.resolveDescription(new Constraint(Past.class
.getName(), Collections.<String, Object> emptyMap()));
assertThat(description, is(equalTo("Must be in the past")));
}
@Test
public void defaultMessagePattern() {
Map<String, Object> configuration = new HashMap<>();
configuration.put("regexp", "[A-Z][a-z]+");
String description = this.resolver.resolveDescription(new Constraint(
Pattern.class.getName(), configuration));
assertThat(description,
is(equalTo("Must match the regular expression '[A-Z][a-z]+'")));
}
@Test
public void defaultMessageSize() {
Map<String, Object> configuration = new HashMap<>();
configuration.put("min", 2);
configuration.put("max", 10);
String description = this.resolver.resolveDescription(new Constraint(Size.class
.getName(), configuration));
assertThat(description, is(equalTo("Size must be between 2 and 10 inclusive")));
}
@Test
public void customMessage() {
Thread.currentThread().setContextClassLoader(new ClassLoader() {
@Override
public URL getResource(String name) {
if (name.startsWith("org/springframework/restdocs/constraints/ConstraintDescriptions")) {
return super
.getResource("org/springframework/restdocs/constraints/TestConstraintDescriptions.properties");
}
return super.getResource(name);
}
});
try {
String description = new ResourceBundleConstraintDescriptionResolver()
.resolveDescription(new Constraint(NotNull.class.getName(),
Collections.<String, Object> emptyMap()));
assertThat(description, is(equalTo("Should not be null")));
}
finally {
Thread.currentThread().setContextClassLoader(getClass().getClassLoader());
}
}
@Test
public void customResourceBundle() {
ResourceBundle bundle = new ListResourceBundle() {
@Override
protected Object[][] getContents() {
return new String[][] { { NotNull.class.getName() + ".description",
"Not null" } };
}
};
String description = new ResourceBundleConstraintDescriptionResolver(bundle)
.resolveDescription(new Constraint(NotNull.class.getName(), Collections
.<String, Object> emptyMap()));
assertThat(description, is(equalTo("Not null")));
}
}

View File

@@ -0,0 +1,164 @@
/*
* 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.constraints;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.hamcrest.Matchers.hasSize;
import static org.junit.Assert.assertThat;
import java.lang.annotation.Annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import javax.validation.Payload;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Null;
import javax.validation.constraints.Size;
import org.hamcrest.BaseMatcher;
import org.hamcrest.Description;
import org.hibernate.validator.constraints.CompositionType;
import org.hibernate.validator.constraints.ConstraintComposition;
import org.hibernate.validator.constraints.NotBlank;
import org.junit.Test;
/**
* Tests for {@link ValidatorConstraintResolver}
*
* @author Andy Wilkinson
*/
public class ValidatorConstraintResolverTests {
private final ValidatorConstraintResolver resolver = new ValidatorConstraintResolver();
@Test
public void singleFieldConstraint() {
List<Constraint> constraints = this.resolver.resolveForProperty("single",
ConstrainedFields.class);
assertThat(constraints, hasSize(1));
assertThat(constraints.get(0).getName(), is(NotNull.class.getName()));
}
@SuppressWarnings("unchecked")
@Test
public void multipleFieldConstraints() {
List<Constraint> constraints = this.resolver.resolveForProperty("multiple",
ConstrainedFields.class);
assertThat(constraints, hasSize(2));
assertThat(
constraints,
containsInAnyOrder(constraint(NotNull.class), constraint(Size.class)
.config("min", 8).config("max", 16)));
}
@Test
public void noFieldConstraints() {
List<Constraint> constraints = this.resolver.resolveForProperty("none",
ConstrainedFields.class);
assertThat(constraints, hasSize(0));
}
@Test
public void compositeConstraint() {
List<Constraint> constraints = this.resolver.resolveForProperty("composite",
ConstrainedFields.class);
assertThat(constraints, hasSize(1));
}
private static class ConstrainedFields {
@NotNull
private String single;
@NotNull
@Size(min = 8, max = 16)
private String multiple;
@SuppressWarnings("unused")
private String none;
@CompositeConstraint
private String composite;
}
@ConstraintComposition(CompositionType.OR)
@Null
@NotBlank
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@javax.validation.Constraint(validatedBy = {})
public @interface CompositeConstraint {
String message() default "Must be null or not blank";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
private ConstraintMatcher constraint(final Class<? extends Annotation> annotation) {
return new ConstraintMatcher(annotation);
}
private static class ConstraintMatcher extends BaseMatcher<Constraint> {
private final Class<?> annotation;
private final Map<String, Object> configuration = new HashMap<>();
private ConstraintMatcher(Class<?> annotation) {
this.annotation = annotation;
}
public ConstraintMatcher config(String key, Object value) {
this.configuration.put(key, value);
return this;
}
@Override
public boolean matches(Object item) {
if (!(item instanceof Constraint)) {
return false;
}
Constraint constraint = (Constraint) item;
if (!constraint.getName().equals(this.annotation.getName())) {
return false;
}
for (Entry<String, Object> entry : this.configuration.entrySet()) {
if (!constraint.getConfiguration().get(entry.getKey())
.equals(entry.getValue())) {
return false;
}
}
return true;
}
@Override
public void describeTo(Description description) {
description.appendText("Constraint named " + this.annotation.getName()
+ " with configuration " + this.configuration);
}
}
}

View File

@@ -0,0 +1,260 @@
/*
* 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.curl;
import static org.hamcrest.CoreMatchers.containsString;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import static org.springframework.restdocs.snippet.Attributes.attributes;
import static org.springframework.restdocs.snippet.Attributes.key;
import static org.springframework.restdocs.test.SnippetMatchers.codeBlock;
import java.io.IOException;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.springframework.core.io.FileSystemResource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.restdocs.templates.TemplateEngine;
import org.springframework.restdocs.templates.TemplateResourceResolver;
import org.springframework.restdocs.templates.mustache.MustacheTemplateEngine;
import org.springframework.restdocs.test.ExpectedSnippet;
import org.springframework.restdocs.test.OperationBuilder;
/**
* Tests for {@link CurlRequestSnippet}
*
* @author Andy Wilkinson
* @author Yann Le Guern
* @author Dmitriy Mayboroda
* @author Jonathan Pearlin
*/
public class CurlRequestSnippetTests {
@Rule
public ExpectedSnippet snippet = new ExpectedSnippet();
@Rule
public ExpectedException thrown = ExpectedException.none();
@Test
public void getRequest() throws IOException {
this.snippet.expectCurlRequest("get-request").withContents(
codeBlock("bash").content("$ curl 'http://localhost/foo' -i"));
new CurlRequestSnippet().document(new OperationBuilder("get-request",
this.snippet.getOutputDirectory()).request("http://localhost/foo")
.build());
}
@Test
public void nonGetRequest() throws IOException {
this.snippet.expectCurlRequest("non-get-request").withContents(
codeBlock("bash").content("$ curl 'http://localhost/foo' -i -X POST"));
new CurlRequestSnippet().document(new OperationBuilder("non-get-request",
this.snippet.getOutputDirectory()).request("http://localhost/foo")
.method("POST").build());
}
@Test
public void requestWithContent() throws IOException {
this.snippet.expectCurlRequest("request-with-content").withContents(
codeBlock("bash")
.content("$ curl 'http://localhost/foo' -i -d 'content'"));
new CurlRequestSnippet().document(new OperationBuilder("request-with-content",
this.snippet.getOutputDirectory()).request("http://localhost/foo")
.content("content").build());
}
@Test
public void requestWithQueryString() throws IOException {
this.snippet.expectCurlRequest("request-with-query-string")
.withContents(
codeBlock("bash").content(
"$ curl 'http://localhost/foo?param=value' -i"));
new CurlRequestSnippet().document(new OperationBuilder(
"request-with-query-string", this.snippet.getOutputDirectory()).request(
"http://localhost/foo?param=value").build());
}
@Test
public void postRequestWithOneParameter() throws IOException {
this.snippet.expectCurlRequest("post-request-with-one-parameter").withContents(
codeBlock("bash").content(
"$ curl 'http://localhost/foo' -i -X POST -d 'k1=v1'"));
new CurlRequestSnippet()
.document(new OperationBuilder("post-request-with-one-parameter",
this.snippet.getOutputDirectory())
.request("http://localhost/foo").method("POST").param("k1", "v1")
.build());
}
@Test
public void postRequestWithMultipleParameters() throws IOException {
this.snippet.expectCurlRequest("post-request-with-multiple-parameters")
.withContents(
codeBlock("bash").content(
"$ curl 'http://localhost/foo' -i -X POST"
+ " -d 'k1=v1&k1=v1-bis&k2=v2'"));
new CurlRequestSnippet().document(new OperationBuilder(
"post-request-with-multiple-parameters", this.snippet
.getOutputDirectory()).request("http://localhost/foo")
.method("POST").param("k1", "v1", "v1-bis").param("k2", "v2").build());
}
@Test
public void postRequestWithUrlEncodedParameter() throws IOException {
this.snippet
.expectCurlRequest("post-request-with-url-encoded-parameter")
.withContents(
codeBlock("bash").content(
"$ curl 'http://localhost/foo' -i -X POST -d 'k1=a%26b'"));
new CurlRequestSnippet().document(new OperationBuilder(
"post-request-with-url-encoded-parameter", this.snippet
.getOutputDirectory()).request("http://localhost/foo")
.method("POST").param("k1", "a&b").build());
}
@Test
public void putRequestWithOneParameter() throws IOException {
this.snippet.expectCurlRequest("put-request-with-one-parameter").withContents(
codeBlock("bash").content(
"$ curl 'http://localhost/foo' -i -X PUT -d 'k1=v1'"));
new CurlRequestSnippet().document(new OperationBuilder(
"put-request-with-one-parameter", this.snippet.getOutputDirectory())
.request("http://localhost/foo").method("PUT").param("k1", "v1").build());
}
@Test
public void putRequestWithMultipleParameters() throws IOException {
this.snippet.expectCurlRequest("put-request-with-multiple-parameters")
.withContents(
codeBlock("bash").content(
"$ curl 'http://localhost/foo' -i -X PUT"
+ " -d 'k1=v1&k1=v1-bis&k2=v2'"));
new CurlRequestSnippet()
.document(new OperationBuilder("put-request-with-multiple-parameters",
this.snippet.getOutputDirectory())
.request("http://localhost/foo").method("PUT").param("k1", "v1")
.param("k1", "v1-bis").param("k2", "v2").build());
}
@Test
public void putRequestWithUrlEncodedParameter() throws IOException {
this.snippet.expectCurlRequest("put-request-with-url-encoded-parameter")
.withContents(
codeBlock("bash").content(
"$ curl 'http://localhost/foo' -i -X PUT -d 'k1=a%26b'"));
new CurlRequestSnippet().document(new OperationBuilder(
"put-request-with-url-encoded-parameter", this.snippet
.getOutputDirectory()).request("http://localhost/foo")
.method("PUT").param("k1", "a&b").build());
}
@Test
public void requestWithHeaders() throws IOException {
this.snippet.expectCurlRequest("request-with-headers").withContents(
codeBlock("bash").content(
"$ curl 'http://localhost/foo' -i"
+ " -H 'Content-Type: application/json' -H 'a: alpha'"));
new CurlRequestSnippet().document(new OperationBuilder("request-with-headers",
this.snippet.getOutputDirectory()).request("http://localhost/foo")
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
.header("a", "alpha").build());
}
@Test
public void multipartPostWithNoSubmittedFileName() throws IOException {
String expectedContent = "$ curl 'http://localhost/upload' -i -X POST -H "
+ "'Content-Type: multipart/form-data' -F "
+ "'metadata={\"description\": \"foo\"}'";
this.snippet.expectCurlRequest("multipart-post-no-original-filename")
.withContents(codeBlock("bash").content(expectedContent));
new CurlRequestSnippet().document(new OperationBuilder(
"multipart-post-no-original-filename", this.snippet.getOutputDirectory())
.request("http://localhost/upload").method("POST")
.header(HttpHeaders.CONTENT_TYPE, MediaType.MULTIPART_FORM_DATA_VALUE)
.part("metadata", "{\"description\": \"foo\"}".getBytes()).build());
}
@Test
public void multipartPostWithContentType() throws IOException {
String expectedContent = "$ curl 'http://localhost/upload' -i -X POST -H "
+ "'Content-Type: multipart/form-data' -F "
+ "'image=@documents/images/example.png;type=image/png'";
this.snippet.expectCurlRequest("multipart-post-with-content-type").withContents(
codeBlock("bash").content(expectedContent));
new CurlRequestSnippet().document(new OperationBuilder(
"multipart-post-with-content-type", this.snippet.getOutputDirectory())
.request("http://localhost/upload").method("POST")
.header(HttpHeaders.CONTENT_TYPE, MediaType.MULTIPART_FORM_DATA_VALUE)
.part("image", new byte[0])
.header(HttpHeaders.CONTENT_TYPE, MediaType.IMAGE_PNG_VALUE)
.submittedFileName("documents/images/example.png").build());
}
@Test
public void multipartPost() throws IOException {
String expectedContent = "$ curl 'http://localhost/upload' -i -X POST -H "
+ "'Content-Type: multipart/form-data' -F "
+ "'image=@documents/images/example.png'";
this.snippet.expectCurlRequest("multipart-post").withContents(
codeBlock("bash").content(expectedContent));
new CurlRequestSnippet().document(new OperationBuilder("multipart-post",
this.snippet.getOutputDirectory()).request("http://localhost/upload")
.method("POST")
.header(HttpHeaders.CONTENT_TYPE, MediaType.MULTIPART_FORM_DATA_VALUE)
.part("image", new byte[0])
.submittedFileName("documents/images/example.png").build());
}
@Test
public void multipartPostWithParameters() throws IOException {
String expectedContent = "$ curl 'http://localhost/upload' -i -X POST -H "
+ "'Content-Type: multipart/form-data' -F "
+ "'image=@documents/images/example.png' -F 'a=apple' -F 'a=avocado' "
+ "-F 'b=banana'";
this.snippet.expectCurlRequest("multipart-post-with-parameters").withContents(
codeBlock("bash").content(expectedContent));
new CurlRequestSnippet().document(new OperationBuilder(
"multipart-post-with-parameters", this.snippet.getOutputDirectory())
.request("http://localhost/upload").method("POST")
.header(HttpHeaders.CONTENT_TYPE, MediaType.MULTIPART_FORM_DATA_VALUE)
.part("image", new byte[0])
.submittedFileName("documents/images/example.png").and()
.param("a", "apple", "avocado").param("b", "banana").build());
}
@Test
public void customAttributes() throws IOException {
this.snippet.expectCurlRequest("custom-attributes").withContents(
containsString("curl request title"));
TemplateResourceResolver resolver = mock(TemplateResourceResolver.class);
when(resolver.resolveTemplateResource("curl-request"))
.thenReturn(
new FileSystemResource(
"src/test/resources/custom-snippet-templates/curl-request-with-title.snippet"));
new CurlRequestSnippet(attributes(key("title").value("curl request title")))
.document(new OperationBuilder("custom-attributes", this.snippet
.getOutputDirectory())
.attribute(TemplateEngine.class.getName(),
new MustacheTemplateEngine(resolver))
.request("http://localhost/foo").build());
}
}

View File

@@ -0,0 +1,227 @@
/*
* 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.http;
import static org.hamcrest.CoreMatchers.containsString;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
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.web.bind.annotation.RequestMethod.GET;
import static org.springframework.web.bind.annotation.RequestMethod.POST;
import static org.springframework.web.bind.annotation.RequestMethod.PUT;
import java.io.IOException;
import org.junit.Rule;
import org.junit.Test;
import org.springframework.core.io.FileSystemResource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.restdocs.templates.TemplateEngine;
import org.springframework.restdocs.templates.TemplateResourceResolver;
import org.springframework.restdocs.templates.mustache.MustacheTemplateEngine;
import org.springframework.restdocs.test.ExpectedSnippet;
import org.springframework.restdocs.test.OperationBuilder;
/**
* Tests for {@link HttpRequestSnippet}
*
* @author Andy Wilkinson
* @author Jonathan Pearlin
*
*/
public class HttpRequestSnippetTests {
private static final String BOUNDARY = "6o2knFse3p53ty9dmcQvWAIx1zInP11uCfbm";
@Rule
public final ExpectedSnippet snippet = new ExpectedSnippet();
@Test
public void getRequest() throws IOException {
this.snippet.expectHttpRequest("get-request").withContents(
httpRequest(GET, "/foo").header(HttpHeaders.HOST, "localhost").header(
"Alpha", "a"));
new HttpRequestSnippet().document(new OperationBuilder("get-request",
this.snippet.getOutputDirectory()).request("http://localhost/foo")
.header("Alpha", "a").build());
}
@Test
public void getRequestWithQueryString() throws IOException {
this.snippet.expectHttpRequest("get-request-with-query-string").withContents(
httpRequest(GET, "/foo?bar=baz").header(HttpHeaders.HOST, "localhost"));
new HttpRequestSnippet().document(new OperationBuilder(
"get-request-with-query-string", this.snippet.getOutputDirectory())
.request("http://localhost/foo?bar=baz").build());
}
@Test
public void postRequestWithContent() throws IOException {
this.snippet.expectHttpRequest("post-request-with-content").withContents(
httpRequest(POST, "/foo").header(HttpHeaders.HOST, "localhost").content(
"Hello, world"));
new HttpRequestSnippet().document(new OperationBuilder(
"post-request-with-content", this.snippet.getOutputDirectory())
.request("http://localhost/foo").method("POST").content("Hello, world")
.build());
}
@Test
public void postRequestWithParameter() throws IOException {
this.snippet.expectHttpRequest("post-request-with-parameter").withContents(
httpRequest(POST, "/foo").header(HttpHeaders.HOST, "localhost")
.header("Content-Type", "application/x-www-form-urlencoded")
.content("b%26r=baz&a=alpha"));
new HttpRequestSnippet().document(new OperationBuilder(
"post-request-with-parameter", this.snippet.getOutputDirectory())
.request("http://localhost/foo").method("POST").param("b&r", "baz")
.param("a", "alpha").build());
}
@Test
public void putRequestWithContent() throws IOException {
this.snippet.expectHttpRequest("put-request-with-content").withContents(
httpRequest(PUT, "/foo").header(HttpHeaders.HOST, "localhost").content(
"Hello, world"));
new HttpRequestSnippet().document(new OperationBuilder(
"put-request-with-content", this.snippet.getOutputDirectory())
.request("http://localhost/foo").method("PUT").content("Hello, world")
.build());
}
@Test
public void putRequestWithParameter() throws IOException {
this.snippet.expectHttpRequest("put-request-with-parameter").withContents(
httpRequest(PUT, "/foo").header(HttpHeaders.HOST, "localhost")
.header("Content-Type", "application/x-www-form-urlencoded")
.content("b%26r=baz&a=alpha"));
new HttpRequestSnippet().document(new OperationBuilder(
"put-request-with-parameter", this.snippet.getOutputDirectory())
.request("http://localhost/foo").method("PUT").param("b&r", "baz")
.param("a", "alpha").build());
}
@Test
public void multipartPost() throws IOException {
String expectedContent = createPart(String.format("Content-Disposition: "
+ "form-data; " + "name=image%n%n<< data >>"));
this.snippet.expectHttpRequest("multipart-post").withContents(
httpRequest(POST, "/upload")
.header(HttpHeaders.HOST, "localhost")
.header("Content-Type",
"multipart/form-data; boundary=" + BOUNDARY)
.content(expectedContent));
new HttpRequestSnippet().document(new OperationBuilder("multipart-post",
this.snippet.getOutputDirectory()).request("http://localhost/upload")
.method("POST")
.header(HttpHeaders.CONTENT_TYPE, MediaType.MULTIPART_FORM_DATA_VALUE)
.part("image", "<< data >>".getBytes()).build());
}
@Test
public void multipartPostWithParameters() throws IOException {
String param1Part = createPart(String.format("Content-Disposition: form-data; "
+ "name=a%n%napple"), false);
String param2Part = createPart(String.format("Content-Disposition: form-data; "
+ "name=a%n%navocado"), false);
String param3Part = createPart(String.format("Content-Disposition: form-data; "
+ "name=b%n%nbanana"), false);
String filePart = createPart(String.format("Content-Disposition: form-data; "
+ "name=image%n%n<< data >>"));
String expectedContent = param1Part + param2Part + param3Part + filePart;
this.snippet.expectHttpRequest("multipart-post-with-parameters").withContents(
httpRequest(POST, "/upload")
.header(HttpHeaders.HOST, "localhost")
.header("Content-Type",
"multipart/form-data; boundary=" + BOUNDARY)
.content(expectedContent));
new HttpRequestSnippet().document(new OperationBuilder(
"multipart-post-with-parameters", this.snippet.getOutputDirectory())
.request("http://localhost/upload").method("POST")
.header(HttpHeaders.CONTENT_TYPE, MediaType.MULTIPART_FORM_DATA_VALUE)
.param("a", "apple", "avocado").param("b", "banana")
.part("image", "<< data >>".getBytes()).build());
}
@Test
public void multipartPostWithContentType() throws IOException {
String expectedContent = createPart(String
.format("Content-Disposition: form-data; name=image%nContent-Type: "
+ "image/png%n%n<< data >>"));
this.snippet.expectHttpRequest("multipart-post-with-content-type").withContents(
httpRequest(POST, "/upload")
.header(HttpHeaders.HOST, "localhost")
.header("Content-Type",
"multipart/form-data; boundary=" + BOUNDARY)
.content(expectedContent));
new HttpRequestSnippet().document(new OperationBuilder(
"multipart-post-with-content-type", this.snippet.getOutputDirectory())
.request("http://localhost/upload").method("POST")
.header(HttpHeaders.CONTENT_TYPE, MediaType.MULTIPART_FORM_DATA_VALUE)
.part("image", "<< data >>".getBytes())
.header(HttpHeaders.CONTENT_TYPE, MediaType.IMAGE_PNG_VALUE).build());
}
@Test
public void getRequestWithCustomHost() throws IOException {
this.snippet.expectHttpRequest("get-request-custom-host").withContents(
httpRequest(GET, "/foo").header(HttpHeaders.HOST, "api.example.com"));
new HttpRequestSnippet().document(new OperationBuilder("get-request-custom-host",
this.snippet.getOutputDirectory()).request("http://localhost/foo")
.header(HttpHeaders.HOST, "api.example.com").build());
}
@Test
public void requestWithCustomSnippetAttributes() throws IOException {
this.snippet.expectHttpRequest("request-with-snippet-attributes").withContents(
containsString("Title for the request"));
TemplateResourceResolver resolver = mock(TemplateResourceResolver.class);
when(resolver.resolveTemplateResource("http-request"))
.thenReturn(
new FileSystemResource(
"src/test/resources/custom-snippet-templates/http-request-with-title.snippet"));
new HttpRequestSnippet(attributes(key("title").value("Title for the request")))
.document(new OperationBuilder("request-with-snippet-attributes",
this.snippet.getOutputDirectory())
.attribute(TemplateEngine.class.getName(),
new MustacheTemplateEngine(resolver))
.request("http://localhost/foo").build());
}
private String createPart(String content) {
return this.createPart(content, true);
}
private String createPart(String content, boolean last) {
StringBuilder part = new StringBuilder();
part.append(String.format("--%s%n%s%n", BOUNDARY, content));
if (last) {
part.append(String.format("--%s--", BOUNDARY));
}
return part.toString();
}
}

View File

@@ -0,0 +1,104 @@
/*
* 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.http;
import static org.hamcrest.CoreMatchers.containsString;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import static org.springframework.http.HttpStatus.BAD_REQUEST;
import static org.springframework.http.HttpStatus.OK;
import static org.springframework.restdocs.snippet.Attributes.attributes;
import static org.springframework.restdocs.snippet.Attributes.key;
import static org.springframework.restdocs.test.SnippetMatchers.httpResponse;
import java.io.IOException;
import org.junit.Rule;
import org.junit.Test;
import org.springframework.core.io.FileSystemResource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.restdocs.templates.TemplateEngine;
import org.springframework.restdocs.templates.TemplateResourceResolver;
import org.springframework.restdocs.templates.mustache.MustacheTemplateEngine;
import org.springframework.restdocs.test.ExpectedSnippet;
import org.springframework.restdocs.test.OperationBuilder;
/**
* Tests for {@link HttpResponseSnippet}
*
* @author Andy Wilkinson
* @author Jonathan Pearlin
*/
public class HttpResponseSnippetTests {
@Rule
public final ExpectedSnippet snippet = new ExpectedSnippet();
@Test
public void basicResponse() throws IOException {
this.snippet.expectHttpResponse("basic-response").withContents(httpResponse(OK));
new HttpResponseSnippet().document(new OperationBuilder("basic-response",
this.snippet.getOutputDirectory()).build());
}
@Test
public void nonOkResponse() throws IOException {
this.snippet.expectHttpResponse("non-ok-response").withContents(
httpResponse(BAD_REQUEST));
new HttpResponseSnippet().document(new OperationBuilder("non-ok-response",
this.snippet.getOutputDirectory()).response().status(BAD_REQUEST.value())
.build());
}
@Test
public void responseWithHeaders() throws IOException {
this.snippet.expectHttpResponse("response-with-headers").withContents(
httpResponse(OK) //
.header("Content-Type", "application/json") //
.header("a", "alpha"));
new HttpResponseSnippet().document(new OperationBuilder("response-with-headers",
this.snippet.getOutputDirectory()).response()
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
.header("a", "alpha").build());
}
@Test
public void responseWithContent() throws IOException {
this.snippet.expectHttpResponse("response-with-content").withContents(
httpResponse(OK).content("content"));
new HttpResponseSnippet().document(new OperationBuilder("response-with-content",
this.snippet.getOutputDirectory()).response().content("content").build());
}
@Test
public void responseWithCustomSnippetAttributes() throws IOException {
this.snippet.expectHttpResponse("response-with-snippet-attributes").withContents(
containsString("Title for the response"));
TemplateResourceResolver resolver = mock(TemplateResourceResolver.class);
when(resolver.resolveTemplateResource("http-response"))
.thenReturn(
new FileSystemResource(
"src/test/resources/custom-snippet-templates/http-response-with-title.snippet"));
new HttpResponseSnippet(attributes(key("title").value("Title for the response")))
.document(new OperationBuilder("response-with-snippet-attributes",
this.snippet.getOutputDirectory()).attribute(
TemplateEngine.class.getName(),
new MustacheTemplateEngine(resolver)).build());
}
}

View File

@@ -0,0 +1,77 @@
/*
* 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.hypermedia;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.restdocs.operation.StandardOperationResponse;
/**
* Tests for {@link ContentTypeLinkExtractor}.
*
* @author Andy Wilkinson
*/
public class ContentTypeLinkExtractorTests {
@Rule
public ExpectedException thrown = ExpectedException.none();
@Test
public void extractionFailsWithNullContentType() throws IOException {
this.thrown.expect(IllegalStateException.class);
new ContentTypeLinkExtractor().extractLinks(new StandardOperationResponse(
HttpStatus.OK, new HttpHeaders(), null));
}
@Test
public void extractorCalledWithMatchingContextType() throws IOException {
Map<MediaType, LinkExtractor> extractors = new HashMap<>();
LinkExtractor extractor = mock(LinkExtractor.class);
extractors.put(MediaType.APPLICATION_JSON, extractor);
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.setContentType(MediaType.APPLICATION_JSON);
StandardOperationResponse response = new StandardOperationResponse(HttpStatus.OK,
httpHeaders, null);
new ContentTypeLinkExtractor(extractors).extractLinks(response);
verify(extractor).extractLinks(response);
}
@Test
public void extractorCalledWithCompatibleContextType() throws IOException {
Map<MediaType, LinkExtractor> extractors = new HashMap<>();
LinkExtractor extractor = mock(LinkExtractor.class);
extractors.put(MediaType.APPLICATION_JSON, extractor);
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.setContentType(MediaType.parseMediaType("application/json;foo=bar"));
StandardOperationResponse response = new StandardOperationResponse(HttpStatus.OK,
httpHeaders, null);
new ContentTypeLinkExtractor(extractors).extractLinks(response);
verify(extractor).extractLinks(response);
}
}

View File

@@ -0,0 +1,118 @@
/*
* 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.hypermedia;
import static org.junit.Assert.assertEquals;
import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
import org.springframework.http.HttpStatus;
import org.springframework.restdocs.operation.OperationResponse;
import org.springframework.restdocs.operation.StandardOperationResponse;
import org.springframework.util.FileCopyUtils;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
/**
* Parameterized tests for {@link HalLinkExtractor} and {@link AtomLinkExtractor} with
* various payloads.
*
* @author Andy Wilkinson
*/
@RunWith(Parameterized.class)
public class LinkExtractorsPayloadTests {
private final LinkExtractor linkExtractor;
private final String linkType;
@Parameters
public static Collection<Object[]> data() {
return Arrays.asList(new Object[] { new HalLinkExtractor(), "hal" },
new Object[] { new AtomLinkExtractor(), "atom" });
}
public LinkExtractorsPayloadTests(LinkExtractor linkExtractor, String linkType) {
this.linkExtractor = linkExtractor;
this.linkType = linkType;
}
@Test
public void singleLink() throws IOException {
Map<String, List<Link>> links = this.linkExtractor
.extractLinks(createResponse("single-link"));
assertLinks(Arrays.asList(new Link("alpha", "http://alpha.example.com")), links);
}
@Test
public void multipleLinksWithDifferentRels() throws IOException {
Map<String, List<Link>> links = this.linkExtractor
.extractLinks(createResponse("multiple-links-different-rels"));
assertLinks(Arrays.asList(new Link("alpha", "http://alpha.example.com"),
new Link("bravo", "http://bravo.example.com")), links);
}
@Test
public void multipleLinksWithSameRels() throws IOException {
Map<String, List<Link>> links = this.linkExtractor
.extractLinks(createResponse("multiple-links-same-rels"));
assertLinks(Arrays.asList(new Link("alpha", "http://alpha.example.com/one"),
new Link("alpha", "http://alpha.example.com/two")), links);
}
@Test
public void noLinks() throws IOException {
Map<String, List<Link>> links = this.linkExtractor
.extractLinks(createResponse("no-links"));
assertLinks(Collections.<Link> emptyList(), links);
}
@Test
public void linksInTheWrongFormat() throws IOException {
Map<String, List<Link>> links = this.linkExtractor
.extractLinks(createResponse("wrong-format"));
assertLinks(Collections.<Link> emptyList(), links);
}
private void assertLinks(List<Link> expectedLinks, Map<String, List<Link>> actualLinks) {
MultiValueMap<String, Link> expectedLinksByRel = new LinkedMultiValueMap<>();
for (Link expectedLink : expectedLinks) {
expectedLinksByRel.add(expectedLink.getRel(), expectedLink);
}
assertEquals(expectedLinksByRel, actualLinks);
}
private OperationResponse createResponse(String contentName) throws IOException {
return new StandardOperationResponse(HttpStatus.OK, null,
FileCopyUtils.copyToByteArray(getPayloadFile(contentName)));
}
private File getPayloadFile(String name) {
return new File("src/test/resources/link-payloads/" + this.linkType + "/" + name
+ ".json");
}
}

View File

@@ -0,0 +1,186 @@
/*
* 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.hypermedia;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.startsWith;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import static org.springframework.restdocs.snippet.Attributes.attributes;
import static org.springframework.restdocs.snippet.Attributes.key;
import static org.springframework.restdocs.test.SnippetMatchers.tableWithHeader;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collections;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.springframework.core.io.FileSystemResource;
import org.springframework.restdocs.operation.OperationResponse;
import org.springframework.restdocs.snippet.SnippetException;
import org.springframework.restdocs.templates.TemplateEngine;
import org.springframework.restdocs.templates.TemplateResourceResolver;
import org.springframework.restdocs.templates.mustache.MustacheTemplateEngine;
import org.springframework.restdocs.test.ExpectedSnippet;
import org.springframework.restdocs.test.OperationBuilder;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
/**
* Tests for {@link LinksSnippet}
*
* @author Andy Wilkinson
*/
public class LinksSnippetTests {
@Rule
public ExpectedSnippet snippet = new ExpectedSnippet();
@Rule
public ExpectedException thrown = ExpectedException.none();
@Test
public void undocumentedLink() throws IOException {
this.thrown.expect(SnippetException.class);
this.thrown.expectMessage(equalTo("Links with the following relations were not"
+ " documented: [foo]"));
new LinksSnippet(new StubLinkExtractor().withLinks(new Link("foo", "bar")),
Collections.<LinkDescriptor> emptyList()).document(new OperationBuilder(
"undocumented-link", this.snippet.getOutputDirectory()).build());
}
@Test
public void missingLink() throws IOException {
this.thrown.expect(SnippetException.class);
this.thrown.expectMessage(equalTo("Links with the following relations were not"
+ " found in the response: [foo]"));
new LinksSnippet(new StubLinkExtractor(), Arrays.asList(new LinkDescriptor("foo")
.description("bar"))).document(new OperationBuilder("missing-link",
this.snippet.getOutputDirectory()).build());
}
@Test
public void documentedOptionalLink() throws IOException {
this.snippet.expectLinks("documented-optional-link").withContents( //
tableWithHeader("Relation", "Description") //
.row("foo", "bar"));
new LinksSnippet(new StubLinkExtractor().withLinks(new Link("foo", "blah")),
Arrays.asList(new LinkDescriptor("foo").description("bar").optional()))
.document(new OperationBuilder("documented-optional-link", this.snippet
.getOutputDirectory()).build());
}
@Test
public void missingOptionalLink() throws IOException {
this.snippet.expectLinks("missing-optional-link").withContents( //
tableWithHeader("Relation", "Description") //
.row("foo", "bar"));
new LinksSnippet(new StubLinkExtractor(), Arrays.asList(new LinkDescriptor("foo")
.description("bar").optional())).document(new OperationBuilder(
"missing-optional-link", this.snippet.getOutputDirectory()).build());
}
@Test
public void undocumentedLinkAndMissingLink() throws IOException {
this.thrown.expect(SnippetException.class);
this.thrown.expectMessage(equalTo("Links with the following relations were not"
+ " documented: [a]. Links with the following relations were not"
+ " found in the response: [foo]"));
new LinksSnippet(new StubLinkExtractor().withLinks(new Link("a", "alpha")),
Arrays.asList(new LinkDescriptor("foo").description("bar")))
.document(new OperationBuilder("undocumented-link-and-missing-link",
this.snippet.getOutputDirectory()).build());
}
@Test
public void documentedLinks() throws IOException {
this.snippet.expectLinks("documented-links").withContents( //
tableWithHeader("Relation", "Description") //
.row("a", "one") //
.row("b", "two"));
new LinksSnippet(new StubLinkExtractor().withLinks(new Link("a", "alpha"),
new Link("b", "bravo")), Arrays.asList(
new LinkDescriptor("a").description("one"),
new LinkDescriptor("b").description("two")))
.document(new OperationBuilder("documented-links", this.snippet
.getOutputDirectory()).build());
}
@Test
public void linksWithCustomDescriptorAttributes() throws IOException {
this.snippet.expectLinks("links-with-custom-descriptor-attributes").withContents( //
tableWithHeader("Relation", "Description", "Foo") //
.row("a", "one", "alpha") //
.row("b", "two", "bravo"));
TemplateResourceResolver resolver = mock(TemplateResourceResolver.class);
when(resolver.resolveTemplateResource("links"))
.thenReturn(
new FileSystemResource(
"src/test/resources/custom-snippet-templates/links-with-extra-column.snippet"));
new LinksSnippet(new StubLinkExtractor().withLinks(new Link("a", "alpha"),
new Link("b", "bravo")), Arrays.asList(
new LinkDescriptor("a").description("one").attributes(
key("foo").value("alpha")),
new LinkDescriptor("b").description("two").attributes(
key("foo").value("bravo")))).document(new OperationBuilder(
"links-with-custom-descriptor-attributes", this.snippet
.getOutputDirectory()).attribute(TemplateEngine.class.getName(),
new MustacheTemplateEngine(resolver)).build());
}
@Test
public void linksWithCustomAttributes() throws IOException {
this.snippet.expectLinks("links-with-custom-attributes").withContents(
startsWith(".Title for the links"));
TemplateResourceResolver resolver = mock(TemplateResourceResolver.class);
when(resolver.resolveTemplateResource("links"))
.thenReturn(
new FileSystemResource(
"src/test/resources/custom-snippet-templates/links-with-title.snippet"));
new LinksSnippet(new StubLinkExtractor().withLinks(new Link("a", "alpha"),
new Link("b", "bravo")), attributes(key("title").value(
"Title for the links")), Arrays.asList(
new LinkDescriptor("a").description("one"),
new LinkDescriptor("b").description("two")))
.document(new OperationBuilder("links-with-custom-attributes",
this.snippet.getOutputDirectory()).attribute(
TemplateEngine.class.getName(),
new MustacheTemplateEngine(resolver)).build());
}
private static class StubLinkExtractor implements LinkExtractor {
private MultiValueMap<String, Link> linksByRel = new LinkedMultiValueMap<String, Link>();
@Override
public MultiValueMap<String, Link> extractLinks(OperationResponse response)
throws IOException {
return this.linksByRel;
}
private StubLinkExtractor withLinks(Link... links) {
for (Link link : links) {
this.linksByRel.add(link.getRel(), link);
}
return this;
}
}
}

View File

@@ -0,0 +1,95 @@
/*
* 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 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.<OperationRequestPart> 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.<OperationRequestPart> 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.<OperationRequestPart> emptyList());
OperationRequest preprocessed = this.preprocessor.preprocess(request);
assertThat(preprocessed.getHeaders().getContentLength(), is(equalTo(8L)));
}
}

View File

@@ -0,0 +1,60 @@
/*
* 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 static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import java.util.Arrays;
import org.junit.Test;
import org.springframework.restdocs.operation.OperationRequest;
/**
* Tests for {@link DelegatingOperationRequestPreprocessor}
*
* @author Andy Wilkinson
*/
public class DelegatingOperationRequestPreprocessorTests {
@Test
public void delegationOccurs() {
OperationRequest originalRequest = mock(OperationRequest.class);
OperationPreprocessor preprocessor1 = mock(OperationPreprocessor.class);
OperationRequest preprocessedRequest1 = mock(OperationRequest.class);
when(preprocessor1.preprocess(originalRequest)).thenReturn(preprocessedRequest1);
OperationPreprocessor preprocessor2 = mock(OperationPreprocessor.class);
OperationRequest preprocessedRequest2 = mock(OperationRequest.class);
when(preprocessor2.preprocess(preprocessedRequest1)).thenReturn(
preprocessedRequest2);
OperationPreprocessor preprocessor3 = mock(OperationPreprocessor.class);
OperationRequest preprocessedRequest3 = mock(OperationRequest.class);
when(preprocessor3.preprocess(preprocessedRequest2)).thenReturn(
preprocessedRequest3);
OperationRequest result = new DelegatingOperationRequestPreprocessor(
Arrays.asList(preprocessor1, preprocessor2, preprocessor3))
.preprocess(originalRequest);
assertThat(result, is(preprocessedRequest3));
}
}

View File

@@ -0,0 +1,61 @@
/*
* 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 static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import java.util.Arrays;
import org.junit.Test;
import org.springframework.restdocs.operation.OperationResponse;
/**
* Tests for {@link DelegatingOperationResponsePreprocessor}
*
* @author Andy Wilkinson
*/
public class DelegatingOperationResponsePreprocessorTests {
@Test
public void delegationOccurs() {
OperationResponse originalResponse = mock(OperationResponse.class);
OperationPreprocessor preprocessor1 = mock(OperationPreprocessor.class);
OperationResponse preprocessedResponse1 = mock(OperationResponse.class);
when(preprocessor1.preprocess(originalResponse))
.thenReturn(preprocessedResponse1);
OperationPreprocessor preprocessor2 = mock(OperationPreprocessor.class);
OperationResponse preprocessedResponse2 = mock(OperationResponse.class);
when(preprocessor2.preprocess(preprocessedResponse1)).thenReturn(
preprocessedResponse2);
OperationPreprocessor preprocessor3 = mock(OperationPreprocessor.class);
OperationResponse preprocessedResponse3 = mock(OperationResponse.class);
when(preprocessor3.preprocess(preprocessedResponse2)).thenReturn(
preprocessedResponse3);
OperationResponse result = new DelegatingOperationResponsePreprocessor(
Arrays.asList(preprocessor1, preprocessor2, preprocessor3))
.preprocess(originalResponse);
assertThat(result, is(preprocessedResponse3));
}
}

View File

@@ -0,0 +1,78 @@
/*
* 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 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.<OperationRequestPart> 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;
}
}

View File

@@ -0,0 +1,156 @@
/*
* 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 static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;
import java.util.Arrays;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import org.junit.Test;
import org.springframework.restdocs.hypermedia.Link;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
/**
* Tests for {@link LinkMaskingContentModifier}
*
* @author Andy Wilkinson
*
*/
public class LinkMaskingContentModifierTests {
private final ContentModifier contentModifier = new LinkMaskingContentModifier();
private final Link[] links = new Link[] { new Link("a", "alpha"),
new Link("b", "bravo") };
private final Link[] maskedLinks = new Link[] { new Link("a", "..."),
new Link("b", "...") };
@Test
public void halLinksAreMasked() throws Exception {
assertThat(this.contentModifier.modifyContent(halPayloadWithLinks(this.links)),
is(equalTo(halPayloadWithLinks(this.maskedLinks))));
}
@Test
public void formattedHalLinksAreMasked() throws Exception {
assertThat(
this.contentModifier
.modifyContent(formattedHalPayloadWithLinks(this.links)),
is(equalTo(formattedHalPayloadWithLinks(this.maskedLinks))));
}
@Test
public void atomLinksAreMasked() throws Exception {
assertThat(this.contentModifier.modifyContent(atomPayloadWithLinks(this.links)),
is(equalTo(atomPayloadWithLinks(this.maskedLinks))));
}
@Test
public void formattedAtomLinksAreMasked() throws Exception {
assertThat(
this.contentModifier
.modifyContent(formattedAtomPayloadWithLinks(this.links)),
is(equalTo(formattedAtomPayloadWithLinks(this.maskedLinks))));
}
@Test
public void maskCanBeCustomized() throws Exception {
assertThat(
new LinkMaskingContentModifier("custom")
.modifyContent(formattedAtomPayloadWithLinks(this.links)),
is(equalTo(formattedAtomPayloadWithLinks(new Link("a", "custom"),
new Link("b", "custom")))));
}
private byte[] atomPayloadWithLinks(Link... links) throws JsonProcessingException {
return new ObjectMapper().writeValueAsBytes(createAtomPayload(links));
}
private byte[] formattedAtomPayloadWithLinks(Link... links)
throws JsonProcessingException {
return new ObjectMapper().configure(SerializationFeature.INDENT_OUTPUT, true)
.writeValueAsBytes(createAtomPayload(links));
}
private AtomPayload createAtomPayload(Link... links) {
AtomPayload payload = new AtomPayload();
payload.setLinks(Arrays.asList(links));
return payload;
}
private byte[] halPayloadWithLinks(Link... links) throws JsonProcessingException {
return new ObjectMapper().writeValueAsBytes(createHalPayload(links));
}
private byte[] formattedHalPayloadWithLinks(Link... links)
throws JsonProcessingException {
return new ObjectMapper().configure(SerializationFeature.INDENT_OUTPUT, true)
.writeValueAsBytes(createHalPayload(links));
}
private HalPayload createHalPayload(Link... links) {
HalPayload payload = new HalPayload();
Map<String, Object> linksMap = new LinkedHashMap<>();
for (Link link : links) {
Map<String, String> linkMap = new HashMap<>();
linkMap.put("href", link.getHref());
linksMap.put(link.getRel(), linkMap);
}
payload.setLinks(linksMap);
return payload;
}
static final class AtomPayload {
private List<Link> links;
public void setLinks(List<Link> links) {
this.links = links;
}
public List<Link> getLinks() {
return this.links;
}
}
static final class HalPayload {
private Map<String, Object> links;
@JsonProperty("_links")
public Map<String, Object> getLinks() {
return this.links;
}
public void setLinks(Map<String, Object> links) {
this.links = links;
}
}
}

View File

@@ -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 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, "<<uuid>>");
assertThat(
contentModifier.modifyContent("{\"id\" : \"CA761232-ED42-11CE-BACD-00AA0057B223\"}"
.getBytes()), is(equalTo("{\"id\" : \"<<uuid>>\"}".getBytes())));
}
}

View File

@@ -0,0 +1,61 @@
/*
* 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 static org.hamcrest.CoreMatchers.equalTo;
import static org.junit.Assert.assertThat;
import org.junit.Test;
/**
* Tests for {@link PrettyPrintingContentModifier}
*
* @author Andy Wilkinson
*
*/
public class PrettyPrintingContentModifierTests {
@Test
public void prettyPrintJson() throws Exception {
assertThat(
new PrettyPrintingContentModifier().modifyContent("{\"a\":5}".getBytes()),
equalTo(String.format("{%n \"a\" : 5%n}").getBytes()));
}
@Test
public void prettyPrintXml() throws Exception {
assertThat(
new PrettyPrintingContentModifier().modifyContent("<one a=\"alpha\"><two b=\"bravo\"/></one>"
.getBytes()), equalTo(String.format(
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>%n"
+ "<one a=\"alpha\">%n <two b=\"bravo\"/>%n</one>%n")
.getBytes()));
}
@Test
public void empytContentIsHandledGracefully() throws Exception {
assertThat(new PrettyPrintingContentModifier().modifyContent("".getBytes()),
equalTo("".getBytes()));
}
@Test
public void nonJsonAndNonXmlContentIsHandledGracefully() throws Exception {
String content = "abcdefg";
assertThat(new PrettyPrintingContentModifier().modifyContent(content.getBytes()),
equalTo(content.getBytes()));
}
}

View File

@@ -0,0 +1,113 @@
/*
* 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.payload;
import static org.hamcrest.Matchers.contains;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import org.junit.Test;
/**
* Tests for {@link JsonFieldPath}
*
* @author Andy Wilkinson
*/
public class JsonFieldPathTests {
@Test
public void singleFieldIsPrecise() {
assertTrue(JsonFieldPath.compile("a").isPrecise());
}
@Test
public void singleNestedFieldIsPrecise() {
assertTrue(JsonFieldPath.compile("a.b").isPrecise());
}
@Test
public void topLevelArrayIsNotPrecise() {
assertFalse(JsonFieldPath.compile("[]").isPrecise());
}
@Test
public void fieldBeneathTopLevelArrayIsNotPrecise() {
assertFalse(JsonFieldPath.compile("[]a").isPrecise());
}
@Test
public void arrayIsNotPrecise() {
assertFalse(JsonFieldPath.compile("a[]").isPrecise());
}
@Test
public void nestedArrayIsNotPrecise() {
assertFalse(JsonFieldPath.compile("a.b[]").isPrecise());
}
@Test
public void arrayOfArraysIsNotPrecise() {
assertFalse(JsonFieldPath.compile("a[][]").isPrecise());
}
@Test
public void fieldBeneathAnArrayIsNotPrecise() {
assertFalse(JsonFieldPath.compile("a[].b").isPrecise());
}
@Test
public void compilationOfSingleElementPath() {
assertThat(JsonFieldPath.compile("a").getSegments(), contains("a"));
}
@Test
public void compilationOfMultipleElementPath() {
assertThat(JsonFieldPath.compile("a.b.c").getSegments(), contains("a", "b", "c"));
}
@Test
public void compilationOfPathWithArraysWithNoDotSeparators() {
assertThat(JsonFieldPath.compile("a[]b[]c").getSegments(),
contains("a", "[]", "b", "[]", "c"));
}
@Test
public void compilationOfPathWithArraysWithPreAndPostDotSeparators() {
assertThat(JsonFieldPath.compile("a.[].b.[].c").getSegments(),
contains("a", "[]", "b", "[]", "c"));
}
@Test
public void compilationOfPathWithArraysWithPreDotSeparators() {
assertThat(JsonFieldPath.compile("a.[]b.[]c").getSegments(),
contains("a", "[]", "b", "[]", "c"));
}
@Test
public void compilationOfPathWithArraysWithPostDotSeparators() {
assertThat(JsonFieldPath.compile("a[].b[].c").getSegments(),
contains("a", "[]", "b", "[]", "c"));
}
@Test
public void compilationOfPathStartingWithAnArray() {
assertThat(JsonFieldPath.compile("[]a.b.c").getSegments(),
contains("[]", "a", "b", "c"));
}
}

View File

@@ -0,0 +1,233 @@
/*
* 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.payload;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.junit.Assert.assertThat;
import java.io.IOException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.junit.Test;
import com.fasterxml.jackson.databind.ObjectMapper;
/**
* Tests for {@link JsonFieldProcessor}
*
* @author Andy Wilkinson
*/
public class JsonFieldProcessorTests {
private final JsonFieldProcessor fieldProcessor = new JsonFieldProcessor();
@Test
public void extractTopLevelMapEntry() {
Map<String, Object> payload = new HashMap<>();
payload.put("a", "alpha");
assertThat(this.fieldProcessor.extract(JsonFieldPath.compile("a"), payload),
equalTo((Object) "alpha"));
}
@Test
public void extractNestedMapEntry() {
Map<String, Object> payload = new HashMap<>();
Map<String, Object> alpha = new HashMap<>();
payload.put("a", alpha);
alpha.put("b", "bravo");
assertThat(this.fieldProcessor.extract(JsonFieldPath.compile("a.b"), payload),
equalTo((Object) "bravo"));
}
@Test
public void extractArray() {
Map<String, Object> payload = new HashMap<>();
Map<String, Object> bravo = new HashMap<>();
bravo.put("b", "bravo");
List<Map<String, Object>> alpha = Arrays.asList(bravo, bravo);
payload.put("a", alpha);
assertThat(this.fieldProcessor.extract(JsonFieldPath.compile("a"), payload),
equalTo((Object) alpha));
}
@Test
public void extractArrayContents() {
Map<String, Object> payload = new HashMap<>();
Map<String, Object> bravo = new HashMap<>();
bravo.put("b", "bravo");
List<Map<String, Object>> alpha = Arrays.asList(bravo, bravo);
payload.put("a", alpha);
assertThat(this.fieldProcessor.extract(JsonFieldPath.compile("a[]"), payload),
equalTo((Object) alpha));
}
@Test
public void extractFromItemsInArray() {
Map<String, Object> payload = new HashMap<>();
Map<String, Object> entry = new HashMap<>();
entry.put("b", "bravo");
List<Map<String, Object>> alpha = Arrays.asList(entry, entry);
payload.put("a", alpha);
assertThat(this.fieldProcessor.extract(JsonFieldPath.compile("a[].b"), payload),
equalTo((Object) Arrays.asList("bravo", "bravo")));
}
@Test
public void extractNestedArray() {
Map<String, Object> payload = new HashMap<>();
Map<String, String> entry1 = createEntry("id:1");
Map<String, String> entry2 = createEntry("id:2");
Map<String, String> entry3 = createEntry("id:3");
List<List<Map<String, String>>> alpha = Arrays.asList(
Arrays.asList(entry1, entry2), Arrays.asList(entry3));
payload.put("a", alpha);
assertThat(this.fieldProcessor.extract(JsonFieldPath.compile("a[][]"), payload),
equalTo((Object) Arrays.asList(entry1, entry2, entry3)));
}
@Test
public void extractFromItemsInNestedArray() {
Map<String, Object> payload = new HashMap<>();
Map<String, String> entry1 = createEntry("id:1");
Map<String, String> entry2 = createEntry("id:2");
Map<String, String> entry3 = createEntry("id:3");
List<List<Map<String, String>>> alpha = Arrays.asList(
Arrays.asList(entry1, entry2), Arrays.asList(entry3));
payload.put("a", alpha);
assertThat(this.fieldProcessor.extract(JsonFieldPath.compile("a[][].id"), payload),
equalTo((Object) Arrays.asList("1", "2", "3")));
}
@Test
public void extractArraysFromItemsInNestedArray() {
Map<String, Object> payload = new HashMap<>();
Map<String, Object> entry1 = createEntry("ids", Arrays.asList(1, 2));
Map<String, Object> entry2 = createEntry("ids", Arrays.asList(3));
Map<String, Object> entry3 = createEntry("ids", Arrays.asList(4));
List<List<Map<String, Object>>> alpha = Arrays.asList(
Arrays.asList(entry1, entry2), Arrays.asList(entry3));
payload.put("a", alpha);
assertThat(this.fieldProcessor.extract(JsonFieldPath.compile("a[][].ids"), payload),
equalTo((Object) Arrays.asList(Arrays.asList(1, 2), Arrays.asList(3),
Arrays.asList(4))));
}
@Test(expected = FieldDoesNotExistException.class)
public void nonExistentTopLevelField() {
this.fieldProcessor
.extract(JsonFieldPath.compile("a"), new HashMap<String, Object>());
}
@Test(expected = FieldDoesNotExistException.class)
public void nonExistentNestedField() {
HashMap<String, Object> payload = new HashMap<String, Object>();
payload.put("a", new HashMap<String, Object>());
this.fieldProcessor.extract(JsonFieldPath.compile("a.b"), payload);
}
@Test(expected = FieldDoesNotExistException.class)
public void nonExistentNestedFieldWhenParentIsNotAMap() {
HashMap<String, Object> payload = new HashMap<String, Object>();
payload.put("a", 5);
this.fieldProcessor.extract(JsonFieldPath.compile("a.b"), payload);
}
@Test(expected = FieldDoesNotExistException.class)
public void nonExistentFieldWhenParentIsAnArray() {
HashMap<String, Object> payload = new HashMap<String, Object>();
HashMap<String, Object> alpha = new HashMap<String, Object>();
alpha.put("b", Arrays.asList(new HashMap<String, Object>()));
payload.put("a", alpha);
this.fieldProcessor.extract(JsonFieldPath.compile("a.b.c"), payload);
}
@Test(expected = FieldDoesNotExistException.class)
public void nonExistentArrayField() {
HashMap<String, Object> payload = new HashMap<String, Object>();
this.fieldProcessor.extract(JsonFieldPath.compile("a[]"), payload);
}
@Test(expected = FieldDoesNotExistException.class)
public void nonExistentArrayFieldAsTypeDoesNotMatch() {
HashMap<String, Object> payload = new HashMap<String, Object>();
payload.put("a", 5);
this.fieldProcessor.extract(JsonFieldPath.compile("a[]"), payload);
}
@Test(expected = FieldDoesNotExistException.class)
public void nonExistentFieldBeneathAnArray() {
HashMap<String, Object> payload = new HashMap<String, Object>();
HashMap<String, Object> alpha = new HashMap<String, Object>();
alpha.put("b", Arrays.asList(new HashMap<String, Object>()));
payload.put("a", alpha);
this.fieldProcessor.extract(JsonFieldPath.compile("a.b[].id"), payload);
}
@Test
public void removeTopLevelMapEntry() {
Map<String, Object> payload = new HashMap<>();
payload.put("a", "alpha");
this.fieldProcessor.remove(JsonFieldPath.compile("a"), payload);
assertThat(payload.size(), equalTo(0));
}
@Test
public void removeNestedMapEntry() {
Map<String, Object> payload = new HashMap<>();
Map<String, Object> alpha = new HashMap<>();
payload.put("a", alpha);
alpha.put("b", "bravo");
this.fieldProcessor.remove(JsonFieldPath.compile("a.b"), payload);
assertThat(payload.size(), equalTo(0));
}
@SuppressWarnings("unchecked")
@Test
public void removeItemsInArray() throws IOException {
Map<String, Object> payload = new ObjectMapper().readValue(
"{\"a\": [{\"b\":\"bravo\"},{\"b\":\"bravo\"}]}", Map.class);
this.fieldProcessor.remove(JsonFieldPath.compile("a[].b"), payload);
assertThat(payload.size(), equalTo(0));
}
@SuppressWarnings("unchecked")
@Test
public void removeItemsInNestedArray() throws IOException {
Map<String, Object> payload = new ObjectMapper().readValue(
"{\"a\": [[{\"id\":1},{\"id\":2}], [{\"id\":3}]]}", Map.class);
this.fieldProcessor.remove(JsonFieldPath.compile("a[][].id"), payload);
assertThat(payload.size(), equalTo(0));
}
private Map<String, String> createEntry(String... pairs) {
Map<String, String> entry = new HashMap<>();
for (String pair : pairs) {
String[] components = pair.split(":");
entry.put(components[0], components[1]);
}
return entry;
}
private Map<String, Object> createEntry(String key, Object value) {
Map<String, Object> entry = new HashMap<>();
entry.put(key, value);
return entry;
}
}

View File

@@ -0,0 +1,117 @@
/*
* 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.payload;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.junit.Assert.assertThat;
import java.io.IOException;
import java.util.Map;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import com.fasterxml.jackson.databind.ObjectMapper;
/**
* Tests for {@link JsonFieldTypeResolver}
*
* @author Andy Wilkinson
*
*/
public class JsonFieldTypeResolverTests {
private final JsonFieldTypeResolver fieldTypeResolver = new JsonFieldTypeResolver();
@Rule
public ExpectedException thrownException = ExpectedException.none();
@Test
public void arrayField() throws IOException {
assertFieldType(JsonFieldType.ARRAY, "[]");
}
@Test
public void booleanField() throws IOException {
assertFieldType(JsonFieldType.BOOLEAN, "true");
}
@Test
public void objectField() throws IOException {
assertFieldType(JsonFieldType.OBJECT, "{}");
}
@Test
public void nullField() throws IOException {
assertFieldType(JsonFieldType.NULL, "null");
}
@Test
public void numberField() throws IOException {
assertFieldType(JsonFieldType.NUMBER, "1.2345");
}
@Test
public void stringField() throws IOException {
assertFieldType(JsonFieldType.STRING, "\"Foo\"");
}
@Test
public void nestedField() throws IOException {
assertThat(this.fieldTypeResolver.resolveFieldType("a.b.c",
createPayload("{\"a\":{\"b\":{\"c\":{}}}}")), equalTo(JsonFieldType.OBJECT));
}
@Test
public void multipleFieldsWithSameType() throws IOException {
assertThat(this.fieldTypeResolver.resolveFieldType("a[].id",
createPayload("{\"a\":[{\"id\":1},{\"id\":2}]}")),
equalTo(JsonFieldType.NUMBER));
}
@Test
public void multipleFieldsWithDifferentTypes() throws IOException {
assertThat(this.fieldTypeResolver.resolveFieldType("a[].id",
createPayload("{\"a\":[{\"id\":1},{\"id\":true}]}")),
equalTo(JsonFieldType.VARIES));
}
@Test
public void nonExistentFieldProducesIllegalArgumentException() throws IOException {
this.thrownException.expect(FieldDoesNotExistException.class);
this.thrownException
.expectMessage("The payload does not contain a field with the path 'a.b'");
this.fieldTypeResolver.resolveFieldType("a.b", createPayload("{\"a\":{}}"));
}
private void assertFieldType(JsonFieldType expectedType, String jsonValue)
throws IOException {
assertThat(this.fieldTypeResolver.resolveFieldType("field",
createSimplePayload(jsonValue)), equalTo(expectedType));
}
private Map<String, Object> createSimplePayload(String value) throws IOException {
return createPayload("{\"field\":" + value + "}");
}
@SuppressWarnings("unchecked")
private Map<String, Object> createPayload(String json) throws IOException {
return new ObjectMapper().readValue(json, Map.class);
}
}

View File

@@ -0,0 +1,266 @@
/*
* 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.payload;
import static org.hamcrest.CoreMatchers.endsWith;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.startsWith;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath;
import static org.springframework.restdocs.snippet.Attributes.attributes;
import static org.springframework.restdocs.snippet.Attributes.key;
import static org.springframework.restdocs.test.SnippetMatchers.tableWithHeader;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collections;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.springframework.core.io.FileSystemResource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.restdocs.snippet.SnippetException;
import org.springframework.restdocs.templates.TemplateEngine;
import org.springframework.restdocs.templates.TemplateResourceResolver;
import org.springframework.restdocs.templates.mustache.MustacheTemplateEngine;
import org.springframework.restdocs.test.ExpectedSnippet;
import org.springframework.restdocs.test.OperationBuilder;
/**
* Tests for {@link RequestFieldsSnippet}
*
* @author Andy Wilkinson
*/
public class RequestFieldsSnippetTests {
@Rule
public final ExpectedException thrown = ExpectedException.none();
@Rule
public final ExpectedSnippet snippet = new ExpectedSnippet();
@Test
public void mapRequestWithFields() throws IOException {
this.snippet.expectRequestFields("map-request-with-fields").withContents( //
tableWithHeader("Path", "Type", "Description") //
.row("a.b", "Number", "one") //
.row("a.c", "String", "two") //
.row("a", "Object", "three"));
new RequestFieldsSnippet(Arrays.asList(fieldWithPath("a.b").description("one"),
fieldWithPath("a.c").description("two"),
fieldWithPath("a").description("three"))).document(new OperationBuilder(
"map-request-with-fields", this.snippet.getOutputDirectory())
.request("http://localhost")
.content("{\"a\": {\"b\": 5, \"c\": \"charlie\"}}").build());
}
@Test
public void arrayRequestWithFields() throws IOException {
this.snippet.expectRequestFields("array-request-with-fields").withContents( //
tableWithHeader("Path", "Type", "Description") //
.row("[]a.b", "Number", "one") //
.row("[]a.c", "String", "two") //
.row("[]a", "Object", "three"));
new RequestFieldsSnippet(Arrays.asList(fieldWithPath("[]a.b").description("one"),
fieldWithPath("[]a.c").description("two"), fieldWithPath("[]a")
.description("three"))).document(new OperationBuilder(
"array-request-with-fields", this.snippet.getOutputDirectory())
.request("http://localhost")
.content("[{\"a\": {\"b\": 5}},{\"a\": {\"c\": \"charlie\"}}]").build());
}
@Test
public void undocumentedRequestField() throws IOException {
this.thrown.expect(SnippetException.class);
this.thrown
.expectMessage(startsWith("The following parts of the payload were not"
+ " documented:"));
new RequestFieldsSnippet(Collections.<FieldDescriptor> emptyList())
.document(new OperationBuilder("undocumented-request-field", this.snippet
.getOutputDirectory()).request("http://localhost")
.content("{\"a\": 5}").build());
}
@Test
public void missingRequestField() throws IOException {
this.thrown.expect(SnippetException.class);
this.thrown
.expectMessage(equalTo("Fields with the following paths were not found"
+ " in the payload: [a.b]"));
new RequestFieldsSnippet(Arrays.asList(fieldWithPath("a.b").description("one")))
.document(new OperationBuilder("missing-request-fields", this.snippet
.getOutputDirectory()).request("http://localhost").content("{}")
.build());
}
@Test
public void missingOptionalRequestFieldWithNoTypeProvided() throws IOException {
this.thrown.expect(FieldTypeRequiredException.class);
new RequestFieldsSnippet(Arrays.asList(fieldWithPath("a.b").description("one")
.optional())).document(new OperationBuilder(
"missing-optional-request-field-with-no-type", this.snippet
.getOutputDirectory()).request("http://localhost").content("{ }")
.build());
}
@Test
public void undocumentedRequestFieldAndMissingRequestField() throws IOException {
this.thrown.expect(SnippetException.class);
this.thrown
.expectMessage(startsWith("The following parts of the payload were not"
+ " documented:"));
this.thrown
.expectMessage(endsWith("Fields with the following paths were not found"
+ " in the payload: [a.b]"));
new RequestFieldsSnippet(Arrays.asList(fieldWithPath("a.b").description("one")))
.document(new OperationBuilder(
"undocumented-request-field-and-missing-request-field",
this.snippet.getOutputDirectory()).request("http://localhost")
.content("{ \"a\": { \"c\": 5 }}").build());
}
@Test
public void requestFieldsWithCustomDescriptorAttributes() throws IOException {
this.snippet.expectRequestFields(
"request-fields-with-custom-descriptor-attributes").withContents( //
tableWithHeader("Path", "Type", "Description", "Foo") //
.row("a.b", "Number", "one", "alpha") //
.row("a.c", "String", "two", "bravo") //
.row("a", "Object", "three", "charlie"));
TemplateResourceResolver resolver = mock(TemplateResourceResolver.class);
when(resolver.resolveTemplateResource("request-fields")).thenReturn(
snippetResource("request-fields-with-extra-column"));
new RequestFieldsSnippet(Arrays.asList(
fieldWithPath("a.b").description("one").attributes(
key("foo").value("alpha")),
fieldWithPath("a.c").description("two").attributes(
key("foo").value("bravo")),
fieldWithPath("a").description("three").attributes(
key("foo").value("charlie")))).document(new OperationBuilder(
"request-fields-with-custom-descriptor-attributes", this.snippet
.getOutputDirectory())
.attribute(TemplateEngine.class.getName(),
new MustacheTemplateEngine(resolver)).request("http://localhost")
.content("{\"a\": {\"b\": 5, \"c\": \"charlie\"}}").build());
}
@Test
public void requestFieldsWithCustomAttributes() throws IOException {
this.snippet.expectRequestFields("request-fields-with-custom-attributes")
.withContents(startsWith(".Custom title"));
TemplateResourceResolver resolver = mock(TemplateResourceResolver.class);
when(resolver.resolveTemplateResource("request-fields")).thenReturn(
snippetResource("request-fields-with-title"));
new RequestFieldsSnippet(attributes(key("title").value("Custom title")),
Arrays.asList(fieldWithPath("a").description("one")))
.document(new OperationBuilder("request-fields-with-custom-attributes",
this.snippet.getOutputDirectory())
.attribute(TemplateEngine.class.getName(),
new MustacheTemplateEngine(resolver))
.request("http://localhost").content("{\"a\": \"foo\"}").build());
}
@Test
public void xmlRequestFields() throws IOException {
this.snippet.expectRequestFields("xml-request").withContents( //
tableWithHeader("Path", "Type", "Description") //
.row("a/b", "b", "one") //
.row("a/c", "c", "two") //
.row("a", "a", "three"));
new RequestFieldsSnippet(Arrays.asList(fieldWithPath("a/b").description("one")
.type("b"), fieldWithPath("a/c").description("two").type("c"),
fieldWithPath("a").description("three").type("a")))
.document(new OperationBuilder("xml-request", this.snippet
.getOutputDirectory())
.request("http://localhost")
.content("<a><b>5</b><c>charlie</c></a>")
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_XML_VALUE)
.build());
}
@Test
public void undocumentedXmlRequestField() throws IOException {
this.thrown.expect(SnippetException.class);
this.thrown
.expectMessage(startsWith("The following parts of the payload were not"
+ " documented:"));
new RequestFieldsSnippet(Collections.<FieldDescriptor> emptyList())
.document(new OperationBuilder("undocumented-xml-request-field",
this.snippet.getOutputDirectory())
.request("http://localhost")
.content("<a><b>5</b></a>")
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_XML_VALUE)
.build());
}
@Test
public void xmlRequestFieldWithNoType() throws IOException {
this.thrown.expect(FieldTypeRequiredException.class);
new RequestFieldsSnippet(Arrays.asList(fieldWithPath("a").description("one")))
.document(new OperationBuilder("missing-xml-request", this.snippet
.getOutputDirectory())
.request("http://localhost")
.content("<a>5</a>")
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_XML_VALUE)
.build());
}
@Test
public void missingXmlRequestField() throws IOException {
this.thrown.expect(SnippetException.class);
this.thrown
.expectMessage(equalTo("Fields with the following paths were not found"
+ " in the payload: [a/b]"));
new RequestFieldsSnippet(Arrays.asList(fieldWithPath("a/b").description("one"),
fieldWithPath("a").description("one"))).document(new OperationBuilder(
"missing-xml-request-fields", this.snippet.getOutputDirectory())
.request("http://localhost").content("<a></a>")
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_XML_VALUE)
.build());
}
@Test
public void undocumentedXmlRequestFieldAndMissingXmlRequestField() throws IOException {
this.thrown.expect(SnippetException.class);
this.thrown
.expectMessage(startsWith("The following parts of the payload were not"
+ " documented:"));
this.thrown
.expectMessage(endsWith("Fields with the following paths were not found"
+ " in the payload: [a/b]"));
new RequestFieldsSnippet(Arrays.asList(fieldWithPath("a/b").description("one")))
.document(new OperationBuilder(
"undocumented-xml-request-field-and-missing-xml-request-field",
this.snippet.getOutputDirectory())
.request("http://localhost")
.content("<a><c>5</c></a>")
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_XML_VALUE)
.build());
}
private FileSystemResource snippetResource(String name) {
return new FileSystemResource("src/test/resources/custom-snippet-templates/"
+ name + ".snippet");
}
}

View File

@@ -0,0 +1,235 @@
/*
* 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.payload;
import static org.hamcrest.CoreMatchers.endsWith;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.startsWith;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath;
import static org.springframework.restdocs.snippet.Attributes.attributes;
import static org.springframework.restdocs.snippet.Attributes.key;
import static org.springframework.restdocs.test.SnippetMatchers.tableWithHeader;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collections;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.springframework.core.io.FileSystemResource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.restdocs.snippet.SnippetException;
import org.springframework.restdocs.templates.TemplateEngine;
import org.springframework.restdocs.templates.TemplateResourceResolver;
import org.springframework.restdocs.templates.mustache.MustacheTemplateEngine;
import org.springframework.restdocs.test.ExpectedSnippet;
import org.springframework.restdocs.test.OperationBuilder;
/**
* Tests for {@link ReponseFieldsSnippet}
*
* @author Andy Wilkinson
*/
public class ResponseFieldsSnippetTests {
@Rule
public final ExpectedException thrown = ExpectedException.none();
@Rule
public final ExpectedSnippet snippet = new ExpectedSnippet();
@Test
public void mapResponseWithFields() throws IOException {
this.snippet.expectResponseFields("map-response-with-fields").withContents(//
tableWithHeader("Path", "Type", "Description") //
.row("id", "Number", "one") //
.row("date", "String", "two") //
.row("assets", "Array", "three") //
.row("assets[]", "Object", "four") //
.row("assets[].id", "Number", "five") //
.row("assets[].name", "String", "six"));
new ResponseFieldsSnippet(Arrays.asList(fieldWithPath("id").description("one"),
fieldWithPath("date").description("two"), fieldWithPath("assets")
.description("three"),
fieldWithPath("assets[]").description("four"),
fieldWithPath("assets[].id").description("five"),
fieldWithPath("assets[].name").description("six")))
.document(new OperationBuilder("map-response-with-fields", this.snippet
.getOutputDirectory())
.response()
.content(
"{\"id\": 67,\"date\": \"2015-01-20\",\"assets\":"
+ " [{\"id\":356,\"name\": \"sample\"}]}")
.build());
}
@Test
public void arrayResponseWithFields() throws IOException {
this.snippet.expectResponseFields("array-response-with-fields").withContents( //
tableWithHeader("Path", "Type", "Description") //
.row("[]a.b", "Number", "one") //
.row("[]a.c", "String", "two") //
.row("[]a", "Object", "three"));
new ResponseFieldsSnippet(Arrays.asList(
fieldWithPath("[]a.b").description("one"), fieldWithPath("[]a.c")
.description("two"), fieldWithPath("[]a").description("three")))
.document(new OperationBuilder("array-response-with-fields", this.snippet
.getOutputDirectory()).response()
.content("[{\"a\": {\"b\": 5}},{\"a\": {\"c\": \"charlie\"}}]")
.build());
}
@Test
public void arrayResponse() throws IOException {
this.snippet.expectResponseFields("array-response").withContents( //
tableWithHeader("Path", "Type", "Description") //
.row("[]", "String", "one"));
new ResponseFieldsSnippet(Arrays.asList(fieldWithPath("[]").description("one")))
.document(new OperationBuilder("array-response", this.snippet
.getOutputDirectory()).response()
.content("[\"a\", \"b\", \"c\"]").build());
}
@Test
public void responseFieldsWithCustomDescriptorAttributes() throws IOException {
this.snippet.expectResponseFields("response-fields-with-custom-attributes")
.withContents( //
tableWithHeader("Path", "Type", "Description", "Foo") //
.row("a.b", "Number", "one", "alpha") //
.row("a.c", "String", "two", "bravo") //
.row("a", "Object", "three", "charlie"));
TemplateResourceResolver resolver = mock(TemplateResourceResolver.class);
when(resolver.resolveTemplateResource("response-fields")).thenReturn(
snippetResource("response-fields-with-extra-column"));
new ResponseFieldsSnippet(Arrays.asList(
fieldWithPath("a.b").description("one").attributes(
key("foo").value("alpha")),
fieldWithPath("a.c").description("two").attributes(
key("foo").value("bravo")),
fieldWithPath("a").description("three").attributes(
key("foo").value("charlie")))).document(new OperationBuilder(
"response-fields-with-custom-attributes", this.snippet
.getOutputDirectory())
.attribute(TemplateEngine.class.getName(),
new MustacheTemplateEngine(resolver)).response()
.content("{\"a\": {\"b\": 5, \"c\": \"charlie\"}}").build());
}
@Test
public void responseFieldsWithCustomAttributes() throws IOException {
this.snippet.expectResponseFields("response-fields-with-custom-attributes")
.withContents(startsWith(".Custom title"));
TemplateResourceResolver resolver = mock(TemplateResourceResolver.class);
when(resolver.resolveTemplateResource("response-fields")).thenReturn(
snippetResource("response-fields-with-title"));
new ResponseFieldsSnippet(attributes(key("title").value("Custom title")),
Arrays.asList(fieldWithPath("a").description("one")))
.document(new OperationBuilder("response-fields-with-custom-attributes",
this.snippet.getOutputDirectory())
.attribute(TemplateEngine.class.getName(),
new MustacheTemplateEngine(resolver)).response()
.content("{\"a\": \"foo\"}").build());
}
@Test
public void xmlResponseFields() throws IOException {
this.snippet.expectResponseFields("xml-response").withContents( //
tableWithHeader("Path", "Type", "Description") //
.row("a/b", "b", "one") //
.row("a/c", "c", "two") //
.row("a", "a", "three"));
new ResponseFieldsSnippet(Arrays.asList(fieldWithPath("a/b").description("one")
.type("b"), fieldWithPath("a/c").description("two").type("c"),
fieldWithPath("a").description("three").type("a")))
.document(new OperationBuilder("xml-response", this.snippet
.getOutputDirectory())
.response()
.content("<a><b>5</b><c>charlie</c></a>")
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_XML_VALUE)
.build());
}
@Test
public void undocumentedXmlResponseField() throws IOException {
this.thrown.expect(SnippetException.class);
this.thrown
.expectMessage(startsWith("The following parts of the payload were not"
+ " documented:"));
new ResponseFieldsSnippet(Collections.<FieldDescriptor> emptyList())
.document(new OperationBuilder("undocumented-xml-response-field",
this.snippet.getOutputDirectory())
.response()
.content("<a><b>5</b></a>")
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_XML_VALUE)
.build());
}
@Test
public void xmlResponseFieldWithNoType() throws IOException {
this.thrown.expect(FieldTypeRequiredException.class);
new ResponseFieldsSnippet(Arrays.asList(fieldWithPath("a").description("one")))
.document(new OperationBuilder("xml-response-no-field-type", this.snippet
.getOutputDirectory())
.response()
.content("<a>5</a>")
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_XML_VALUE)
.build());
}
@Test
public void missingXmlResponseField() throws IOException {
this.thrown.expect(SnippetException.class);
this.thrown
.expectMessage(equalTo("Fields with the following paths were not found"
+ " in the payload: [a/b]"));
new ResponseFieldsSnippet(Arrays.asList(fieldWithPath("a/b").description("one"),
fieldWithPath("a").description("one"))).document(new OperationBuilder(
"missing-xml-response-field", this.snippet.getOutputDirectory())
.response().content("<a></a>")
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_XML_VALUE)
.build());
}
@Test
public void undocumentedXmlResponseFieldAndMissingXmlResponseField()
throws IOException {
this.thrown.expect(SnippetException.class);
this.thrown
.expectMessage(startsWith("The following parts of the payload were not"
+ " documented:"));
this.thrown
.expectMessage(endsWith("Fields with the following paths were not found"
+ " in the payload: [a/b]"));
new ResponseFieldsSnippet(Arrays.asList(fieldWithPath("a/b").description("one")))
.document(new OperationBuilder(
"undocumented-xml-request-field-and-missing-xml-request-field",
this.snippet.getOutputDirectory())
.response()
.content("<a><c>5</c></a>")
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_XML_VALUE)
.build());
}
private FileSystemResource snippetResource(String name) {
return new FileSystemResource("src/test/resources/custom-snippet-templates/"
+ name + ".snippet");
}
}

View File

@@ -0,0 +1,168 @@
/*
* 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.request;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.startsWith;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName;
import static org.springframework.restdocs.snippet.Attributes.attributes;
import static org.springframework.restdocs.snippet.Attributes.key;
import static org.springframework.restdocs.test.SnippetMatchers.tableWithHeader;
import static org.springframework.restdocs.test.SnippetMatchers.tableWithTitleAndHeader;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collections;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.springframework.core.io.FileSystemResource;
import org.springframework.restdocs.snippet.SnippetException;
import org.springframework.restdocs.templates.TemplateEngine;
import org.springframework.restdocs.templates.TemplateResourceResolver;
import org.springframework.restdocs.templates.mustache.MustacheTemplateEngine;
import org.springframework.restdocs.test.ExpectedSnippet;
import org.springframework.restdocs.test.OperationBuilder;
/**
* Tests for {@link PathParametersSnippet}
*
* @author awilkinson
*
*/
public class PathParametersSnippetTests {
@Rule
public ExpectedException thrown = ExpectedException.none();
@Rule
public ExpectedSnippet snippet = new ExpectedSnippet();
@Test
public void undocumentedPathParameter() throws IOException {
this.thrown.expect(SnippetException.class);
this.thrown.expectMessage(equalTo("Path parameters with the following names were"
+ " not documented: [a]"));
new PathParametersSnippet(Collections.<ParameterDescriptor> emptyList())
.document(new OperationBuilder("undocumented-path-parameter",
this.snippet.getOutputDirectory()).attribute(
"org.springframework.restdocs.urlTemplate", "/{a}/").build());
}
@Test
public void missingPathParameter() throws IOException {
this.thrown.expect(SnippetException.class);
this.thrown.expectMessage(equalTo("Path parameters with the following names were"
+ " not found in the request: [a]"));
new PathParametersSnippet(
Arrays.asList(parameterWithName("a").description("one")))
.document(new OperationBuilder("missing-path-parameter", this.snippet
.getOutputDirectory()).attribute(
"org.springframework.restdocs.urlTemplate", "/").build());
}
@Test
public void undocumentedAndMissingPathParameters() throws IOException {
this.thrown.expect(SnippetException.class);
this.thrown.expectMessage(equalTo("Path parameters with the following names were"
+ " not documented: [b]. Path parameters with the following"
+ " names were not found in the request: [a]"));
new PathParametersSnippet(
Arrays.asList(parameterWithName("a").description("one")))
.document(new OperationBuilder(
"undocumented-and-missing-path-parameters", this.snippet
.getOutputDirectory()).attribute(
"org.springframework.restdocs.urlTemplate", "/{b}").build());
}
@Test
public void pathParameters() throws IOException {
this.snippet.expectPathParameters("path-parameters").withContents(
tableWithTitleAndHeader("/{a}/{b}", "Parameter", "Description").row("a",
"one").row("b", "two"));
new PathParametersSnippet(Arrays.asList(
parameterWithName("a").description("one"), parameterWithName("b")
.description("two"))).document(new OperationBuilder(
"path-parameters", this.snippet.getOutputDirectory()).attribute(
"org.springframework.restdocs.urlTemplate", "/{a}/{b}").build());
}
@Test
public void pathParametersWithQueryString() throws IOException {
this.snippet.expectPathParameters("path-parameters-with-query-string")
.withContents(
tableWithTitleAndHeader("/{a}/{b}", "Parameter", "Description")
.row("a", "one").row("b", "two"));
new PathParametersSnippet(Arrays.asList(
parameterWithName("a").description("one"), parameterWithName("b")
.description("two")))
.document(new OperationBuilder("path-parameters-with-query-string",
this.snippet.getOutputDirectory()).attribute(
"org.springframework.restdocs.urlTemplate", "/{a}/{b}?foo=bar")
.build());
}
@Test
public void pathParametersWithCustomDescriptorAttributes() throws IOException {
this.snippet.expectPathParameters(
"path-parameters-with-custom-descriptor-attributes").withContents(
tableWithHeader("Parameter", "Description", "Foo").row("a", "one",
"alpha").row("b", "two", "bravo"));
TemplateResourceResolver resolver = mock(TemplateResourceResolver.class);
when(resolver.resolveTemplateResource("path-parameters")).thenReturn(
snippetResource("path-parameters-with-extra-column"));
new PathParametersSnippet(Arrays.asList(parameterWithName("a").description("one")
.attributes(key("foo").value("alpha")), parameterWithName("b")
.description("two").attributes(key("foo").value("bravo"))))
.document(new OperationBuilder(
"path-parameters-with-custom-descriptor-attributes", this.snippet
.getOutputDirectory())
.attribute("org.springframework.restdocs.urlTemplate", "/{a}/{b}")
.attribute(TemplateEngine.class.getName(),
new MustacheTemplateEngine(resolver)).build());
}
@Test
public void pathParametersWithCustomAttributes() throws IOException {
this.snippet.expectPathParameters("path-parameters-with-custom-attributes")
.withContents(startsWith(".The title"));
TemplateResourceResolver resolver = mock(TemplateResourceResolver.class);
when(resolver.resolveTemplateResource("path-parameters")).thenReturn(
snippetResource("path-parameters-with-title"));
new PathParametersSnippet(
attributes(key("title").value("The title")),
Arrays.asList(
parameterWithName("a").description("one").attributes(
key("foo").value("alpha")), parameterWithName("b")
.description("two").attributes(key("foo").value("bravo"))))
.document(new OperationBuilder("path-parameters-with-custom-attributes",
this.snippet.getOutputDirectory())
.attribute("org.springframework.restdocs.urlTemplate", "/{a}/{b}")
.attribute(TemplateEngine.class.getName(),
new MustacheTemplateEngine(resolver)).build());
}
private FileSystemResource snippetResource(String name) {
return new FileSystemResource("src/test/resources/custom-snippet-templates/"
+ name + ".snippet");
}
}

View File

@@ -0,0 +1,153 @@
/*
* 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.request;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.startsWith;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName;
import static org.springframework.restdocs.snippet.Attributes.attributes;
import static org.springframework.restdocs.snippet.Attributes.key;
import static org.springframework.restdocs.test.SnippetMatchers.tableWithHeader;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collections;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.springframework.core.io.FileSystemResource;
import org.springframework.restdocs.snippet.SnippetException;
import org.springframework.restdocs.templates.TemplateEngine;
import org.springframework.restdocs.templates.TemplateResourceResolver;
import org.springframework.restdocs.templates.mustache.MustacheTemplateEngine;
import org.springframework.restdocs.test.ExpectedSnippet;
import org.springframework.restdocs.test.OperationBuilder;
/**
* Tests for {@link RequestParametersSnippet}
*
* @author Andy Wilkinson
*/
public class RequestParametersSnippetTests {
@Rule
public ExpectedException thrown = ExpectedException.none();
@Rule
public ExpectedSnippet snippet = new ExpectedSnippet();
@Test
public void undocumentedParameter() throws IOException {
this.thrown.expect(SnippetException.class);
this.thrown
.expectMessage(equalTo("Request parameters with the following names were"
+ " not documented: [a]"));
new RequestParametersSnippet(Collections.<ParameterDescriptor> emptyList())
.document(new OperationBuilder("undocumented-parameter", this.snippet
.getOutputDirectory()).request("http://localhost")
.param("a", "alpha").build());
}
@Test
public void missingParameter() throws IOException {
this.thrown.expect(SnippetException.class);
this.thrown
.expectMessage(equalTo("Request parameters with the following names were"
+ " not found in the request: [a]"));
new RequestParametersSnippet(Arrays.asList(parameterWithName("a").description(
"one"))).document(new OperationBuilder("missing-parameter", this.snippet
.getOutputDirectory()).request("http://localhost").build());
}
@Test
public void undocumentedAndMissingParameters() throws IOException {
this.thrown.expect(SnippetException.class);
this.thrown
.expectMessage(equalTo("Request parameters with the following names were"
+ " not documented: [b]. Request parameters with the following"
+ " names were not found in the request: [a]"));
new RequestParametersSnippet(Arrays.asList(parameterWithName("a").description(
"one"))).document(new OperationBuilder(
"undocumented-and-missing-parameters", this.snippet.getOutputDirectory())
.request("http://localhost").param("b", "bravo").build());
}
@Test
public void requestParameters() throws IOException {
this.snippet.expectRequestParameters("request-parameters").withContents(
tableWithHeader("Parameter", "Description").row("a", "one").row("b",
"two"));
new RequestParametersSnippet(Arrays.asList(
parameterWithName("a").description("one"), parameterWithName("b")
.description("two"))).document(new OperationBuilder(
"request-parameters", this.snippet.getOutputDirectory())
.request("http://localhost").param("a", "bravo").param("b", "bravo")
.build());
}
@Test
public void requestParametersWithCustomDescriptorAttributes() throws IOException {
this.snippet.expectRequestParameters(
"request-parameters-with-custom-descriptor-attributes").withContents(
tableWithHeader("Parameter", "Description", "Foo").row("a", "one",
"alpha").row("b", "two", "bravo"));
TemplateResourceResolver resolver = mock(TemplateResourceResolver.class);
when(resolver.resolveTemplateResource("request-parameters")).thenReturn(
snippetResource("request-parameters-with-extra-column"));
new RequestParametersSnippet(Arrays.asList(
parameterWithName("a").description("one").attributes(
key("foo").value("alpha")),
parameterWithName("b").description("two").attributes(
key("foo").value("bravo")))).document(new OperationBuilder(
"request-parameters-with-custom-descriptor-attributes", this.snippet
.getOutputDirectory())
.attribute(TemplateEngine.class.getName(),
new MustacheTemplateEngine(resolver)).request("http://localhost")
.param("a", "alpha").param("b", "bravo").build());
}
@Test
public void requestParametersWithCustomAttributes() throws IOException {
this.snippet.expectRequestParameters("request-parameters-with-custom-attributes")
.withContents(startsWith(".The title"));
TemplateResourceResolver resolver = mock(TemplateResourceResolver.class);
when(resolver.resolveTemplateResource("request-parameters")).thenReturn(
snippetResource("request-parameters-with-title"));
new RequestParametersSnippet(
attributes(key("title").value("The title")),
Arrays.asList(
parameterWithName("a").description("one").attributes(
key("foo").value("alpha")), parameterWithName("b")
.description("two").attributes(key("foo").value("bravo"))))
.document(new OperationBuilder(
"request-parameters-with-custom-attributes", this.snippet
.getOutputDirectory())
.attribute(TemplateEngine.class.getName(),
new MustacheTemplateEngine(resolver))
.request("http://localhost").param("a", "alpha")
.param("b", "bravo").build());
}
private FileSystemResource snippetResource(String name) {
return new FileSystemResource("src/test/resources/custom-snippet-templates/"
+ name + ".snippet");
}
}

View File

@@ -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.snippet;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.junit.Assert.assertThat;
import org.junit.Test;
import org.springframework.restdocs.RestDocumentationContext;
import org.springframework.util.PropertyPlaceholderHelper.PlaceholderResolver;
/**
* Tests for {@link RestDocumentationContextPlaceholderResolver}
*
* @author Andy Wilkinson
*
*/
public class RestDocumentationContextPlaceholderResolverTests {
@Test
public void dashSeparatedMethodName() throws Exception {
assertThat(
createResolver("dashSeparatedMethodName").resolvePlaceholder(
"method-name"), equalTo("dash-separated-method-name"));
}
@Test
public void underscoreSeparatedMethodName() throws Exception {
assertThat(
createResolver("underscoreSeparatedMethodName").resolvePlaceholder(
"method_name"), equalTo("underscore_separated_method_name"));
}
@Test
public void camelCaseMethodName() throws Exception {
assertThat(
createResolver("camelCaseMethodName").resolvePlaceholder("methodName"),
equalTo("camelCaseMethodName"));
}
@Test
public void stepCount() throws Exception {
assertThat(createResolver("stepCount").resolvePlaceholder("step"), equalTo("0"));
}
private PlaceholderResolver createResolver(String methodName) {
return new RestDocumentationContextPlaceholderResolver(
new RestDocumentationContext(null, methodName, null));
}
}

View File

@@ -0,0 +1,73 @@
/*
* 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.snippet;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.nullValue;
import static org.junit.Assert.assertThat;
import static org.mockito.Mockito.mock;
import java.io.File;
import org.junit.Test;
import org.springframework.restdocs.RestDocumentationContext;
import org.springframework.util.PropertyPlaceholderHelper.PlaceholderResolver;
/**
* Tests for {@link StandardWriterResolver}.
*
* @author Andy Wilkinson
*/
public class StandardWriterResolverTests {
private final PlaceholderResolver placeholderResolver = mock(PlaceholderResolver.class);
private final StandardWriterResolver resolver = new StandardWriterResolver(
this.placeholderResolver);
@Test
public void noConfiguredOutputDirectoryAndRelativeInput() {
assertThat(this.resolver.resolveFile("foo", "bar.txt",
new RestDocumentationContext(null, null, null)), is(nullValue()));
}
@Test
public void absoluteInput() {
String absolutePath = new File("foo").getAbsolutePath();
assertThat(this.resolver.resolveFile(absolutePath, "bar.txt",
new RestDocumentationContext(null, null, null)), is(new File(
absolutePath, "bar.txt")));
}
@Test
public void configuredOutputAndRelativeInput() {
File outputDir = new File("foo").getAbsoluteFile();
assertThat(this.resolver.resolveFile("bar", "baz.txt",
new RestDocumentationContext(null, null, outputDir)), is(new File(
outputDir, "bar/baz.txt")));
}
@Test
public void configuredOutputAndAbsoluteInput() {
File outputDir = new File("foo").getAbsoluteFile();
String absolutePath = new File("bar").getAbsolutePath();
assertThat(this.resolver.resolveFile(absolutePath, "baz.txt",
new RestDocumentationContext(null, null, outputDir)), is(new File(
absolutePath, "baz.txt")));
}
}

View File

@@ -0,0 +1,133 @@
/*
* 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.test;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;
import java.io.File;
import java.io.IOException;
import org.hamcrest.Matcher;
import org.junit.rules.TestRule;
import org.junit.runner.Description;
import org.junit.runners.model.Statement;
import org.springframework.restdocs.snippet.TemplatedSnippet;
import org.springframework.restdocs.test.SnippetMatchers.SnippetMatcher;
/**
* The {@code ExpectedSnippet} rule is used to verify that a {@link TemplatedSnippet} has
* generated the expected snippet.
*
* @author Andy Wilkinson
*/
public class ExpectedSnippet implements TestRule {
private String expectedName;
private String expectedType;
private SnippetMatcher snippet = SnippetMatchers.snippet();
private File outputDirectory;
@Override
public Statement apply(final Statement base, Description description) {
this.outputDirectory = new File("build/"
+ description.getTestClass().getSimpleName());
return new ExpectedSnippetStatement(base);
}
private final class ExpectedSnippetStatement extends Statement {
private final Statement delegate;
public ExpectedSnippetStatement(Statement delegate) {
this.delegate = delegate;
}
@Override
public void evaluate() throws Throwable {
this.delegate.evaluate();
verifySnippet();
}
}
private void verifySnippet() throws IOException {
if (this.outputDirectory != null && this.expectedName != null) {
File snippetDir = new File(this.outputDirectory, this.expectedName);
File snippetFile = new File(snippetDir, this.expectedType + ".adoc");
assertThat(snippetFile, is(this.snippet));
}
}
public ExpectedSnippet expectCurlRequest(String name) {
expect(name, "curl-request");
return this;
}
public ExpectedSnippet expectRequestFields(String name) {
expect(name, "request-fields");
return this;
}
public ExpectedSnippet expectResponseFields(String name) {
expect(name, "response-fields");
return this;
}
public ExpectedSnippet expectLinks(String name) {
expect(name, "links");
return this;
}
public ExpectedSnippet expectHttpRequest(String name) {
expect(name, "http-request");
return this;
}
public ExpectedSnippet expectHttpResponse(String name) {
expect(name, "http-response");
return this;
}
public ExpectedSnippet expectRequestParameters(String name) {
expect(name, "request-parameters");
return this;
}
public ExpectedSnippet expectPathParameters(String name) {
expect(name, "path-parameters");
return this;
}
private ExpectedSnippet expect(String name, String type) {
this.expectedName = name;
this.expectedType = type;
return this;
}
public void withContents(Matcher<String> matcher) {
this.snippet.withContents(matcher);
}
public File getOutputDirectory() {
return this.outputDirectory;
}
}

View File

@@ -0,0 +1,232 @@
/*
* 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.test;
import java.io.File;
import java.net.URI;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.restdocs.RestDocumentationContext;
import org.springframework.restdocs.operation.Operation;
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.StandardOperation;
import org.springframework.restdocs.operation.StandardOperationRequest;
import org.springframework.restdocs.operation.StandardOperationRequestPart;
import org.springframework.restdocs.operation.StandardOperationResponse;
import org.springframework.restdocs.snippet.RestDocumentationContextPlaceholderResolver;
import org.springframework.restdocs.snippet.StandardWriterResolver;
import org.springframework.restdocs.snippet.WriterResolver;
import org.springframework.restdocs.templates.StandardTemplateResourceResolver;
import org.springframework.restdocs.templates.TemplateEngine;
import org.springframework.restdocs.templates.mustache.MustacheTemplateEngine;
public class OperationBuilder {
private final Map<String, Object> attributes = new HashMap<>();
private final OperationResponseBuilder responseBuilder = new OperationResponseBuilder();
private final String name;
private final File outputDirectory;
private OperationRequestBuilder requestBuilder;
public OperationBuilder(String name, File outputDirectory) {
this.name = name;
this.outputDirectory = outputDirectory;
}
public OperationRequestBuilder request(String uri) {
this.requestBuilder = new OperationRequestBuilder(uri);
return this.requestBuilder;
}
public OperationResponseBuilder response() {
return this.responseBuilder;
}
public OperationBuilder attribute(String name, Object value) {
this.attributes.put(name, value);
return this;
}
public Operation build() {
if (this.attributes.get(TemplateEngine.class.getName()) == null) {
this.attributes.put(TemplateEngine.class.getName(),
new MustacheTemplateEngine(new StandardTemplateResourceResolver()));
}
RestDocumentationContext context = new RestDocumentationContext(null, null,
this.outputDirectory);
this.attributes.put(RestDocumentationContext.class.getName(), context);
this.attributes.put(WriterResolver.class.getName(), new StandardWriterResolver(
new RestDocumentationContextPlaceholderResolver(context)));
return new StandardOperation(this.name,
(this.requestBuilder == null ? new OperationRequestBuilder(
"http://localhost/").buildRequest() : this.requestBuilder
.buildRequest()),
this.responseBuilder.buildResponse(), this.attributes);
}
public class OperationRequestBuilder {
private URI requestUri = URI.create("http://localhost/");
private HttpMethod method = HttpMethod.GET;
private byte[] content = new byte[0];
private HttpHeaders headers = new HttpHeaders();
private Parameters parameters = new Parameters();
private List<OperationRequestPartBuilder> partBuilders = new ArrayList<>();
public OperationRequestBuilder(String uri) {
this.requestUri = URI.create(uri);
}
private OperationRequest buildRequest() {
List<OperationRequestPart> parts = new ArrayList<>();
for (OperationRequestPartBuilder builder : this.partBuilders) {
parts.add(builder.buildPart());
}
return new StandardOperationRequest(this.requestUri, this.method,
this.content, this.headers, this.parameters, parts);
}
public Operation build() {
return OperationBuilder.this.build();
}
public OperationRequestBuilder method(String method) {
this.method = HttpMethod.valueOf(method);
return this;
}
public OperationRequestBuilder content(String content) {
this.content = content.getBytes();
return this;
}
public OperationRequestBuilder param(String name, String... values) {
for (String value : values) {
this.parameters.add(name, value);
}
return this;
}
public OperationRequestBuilder header(String name, String value) {
this.headers.add(name, value);
return this;
}
public OperationRequestPartBuilder part(String name, byte[] content) {
OperationRequestPartBuilder partBuilder = new OperationRequestPartBuilder(
name, content);
this.partBuilders.add(partBuilder);
return partBuilder;
}
public class OperationRequestPartBuilder {
private final String name;
private final byte[] content;
private String submittedFileName;
private HttpHeaders headers = new HttpHeaders();
private OperationRequestPartBuilder(String name, byte[] content) {
this.name = name;
this.content = content;
}
public OperationRequestPartBuilder submittedFileName(String submittedFileName) {
this.submittedFileName = submittedFileName;
return this;
}
public OperationRequestBuilder and() {
return OperationRequestBuilder.this;
}
public Operation build() {
return OperationBuilder.this.build();
}
private OperationRequestPart buildPart() {
return new StandardOperationRequestPart(this.name,
this.submittedFileName, this.content, this.headers);
}
public OperationRequestPartBuilder header(String name, String value) {
this.headers.add(name, value);
return this;
}
}
}
public class OperationResponseBuilder {
private HttpStatus status = HttpStatus.OK;
private HttpHeaders headers = new HttpHeaders();
private byte[] content = new byte[0];
private OperationResponse buildResponse() {
return new StandardOperationResponse(this.status, this.headers, this.content);
}
public OperationResponseBuilder status(int status) {
this.status = HttpStatus.valueOf(status);
return this;
}
public OperationResponseBuilder header(String name, String value) {
this.headers.add(name, value);
return this;
}
public OperationResponseBuilder content(byte[] content) {
this.content = content;
return this;
}
public OperationResponseBuilder content(String content) {
this.content = content.getBytes();
return this;
}
public Operation build() {
return OperationBuilder.this.build();
}
}
}

View File

@@ -0,0 +1,257 @@
/*
* 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.test;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import org.hamcrest.BaseMatcher;
import org.hamcrest.Description;
import org.hamcrest.Matcher;
import org.springframework.http.HttpStatus;
import org.springframework.util.FileCopyUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestMethod;
/**
* {@link Matcher Matchers} for verify the contents of generated documentation snippets.
*
* @author Andy Wilkinson
*/
public class SnippetMatchers {
public static SnippetMatcher snippet() {
return new SnippetMatcher();
}
public static AsciidoctorTableMatcher tableWithTitleAndHeader(String title,
String... headers) {
return new AsciidoctorTableMatcher(title, headers);
}
public static AsciidoctorTableMatcher tableWithHeader(String... headers) {
return new AsciidoctorTableMatcher(null, headers);
}
public static HttpRequestMatcher httpRequest(RequestMethod method, String uri) {
return new HttpRequestMatcher(method, uri);
}
public static HttpResponseMatcher httpResponse(HttpStatus status) {
return new HttpResponseMatcher(status);
}
@SuppressWarnings({ "rawtypes" })
public static AsciidoctorCodeBlockMatcher<?> codeBlock(String language) {
return new AsciidoctorCodeBlockMatcher(language);
}
private static abstract class AbstractSnippetContentMatcher extends
BaseMatcher<String> {
private List<String> lines = new ArrayList<String>();
protected void addLine(String line) {
this.lines.add(line);
}
protected void addLine(int index, String line) {
if (index < 0) {
index = index + this.lines.size();
}
this.lines.add(index, line);
}
@Override
public boolean matches(Object item) {
return getLinesAsString().equals(item);
}
@Override
public void describeTo(Description description) {
description.appendText("Asciidoctor snippet");
description.appendText(getLinesAsString());
}
@Override
public void describeMismatch(Object item, Description description) {
description.appendText("was:");
if (item instanceof String) {
description.appendText((String) item);
}
else {
description.appendValue(item);
}
}
private String getLinesAsString() {
StringWriter writer = new StringWriter();
Iterator<String> iterator = this.lines.iterator();
while (iterator.hasNext()) {
writer.append(String.format("%s", iterator.next()));
if (iterator.hasNext()) {
writer.append(String.format("%n"));
}
}
return writer.toString();
}
}
public static class AsciidoctorCodeBlockMatcher<T extends AsciidoctorCodeBlockMatcher<T>>
extends AbstractSnippetContentMatcher {
protected AsciidoctorCodeBlockMatcher(String language) {
this.addLine("[source," + language + "]");
this.addLine("----");
this.addLine("----");
}
@SuppressWarnings("unchecked")
public T content(String content) {
this.addLine(-1, content);
return (T) this;
}
}
public static abstract class HttpMatcher<T extends HttpMatcher<T>> extends
AsciidoctorCodeBlockMatcher<HttpMatcher<T>> {
private int headerOffset = 3;
protected HttpMatcher() {
super("http");
}
@SuppressWarnings("unchecked")
public T header(String name, String value) {
this.addLine(this.headerOffset++, name + ": " + value);
return (T) this;
}
}
public static class HttpResponseMatcher extends HttpMatcher<HttpResponseMatcher> {
public HttpResponseMatcher(HttpStatus status) {
this.content("HTTP/1.1 " + status.value() + " " + status.getReasonPhrase());
this.content("");
}
}
public static class HttpRequestMatcher extends HttpMatcher<HttpRequestMatcher> {
public HttpRequestMatcher(RequestMethod requestMethod, String uri) {
this.content(requestMethod.name() + " " + uri + " HTTP/1.1");
this.content("");
}
}
public static class AsciidoctorTableMatcher extends AbstractSnippetContentMatcher {
private AsciidoctorTableMatcher(String title, String... columns) {
if (StringUtils.hasText(title)) {
this.addLine("." + title);
}
this.addLine("|===");
String header = "|"
+ StringUtils
.collectionToDelimitedString(Arrays.asList(columns), "|");
this.addLine(header);
this.addLine("");
this.addLine("|===");
}
public AsciidoctorTableMatcher row(String... entries) {
for (String entry : entries) {
this.addLine(-1, "|" + entry);
}
this.addLine(-1, "");
return this;
}
}
public static class SnippetMatcher extends BaseMatcher<File> {
private Matcher<String> expectedContents;
@Override
public boolean matches(Object item) {
if (snippetFileExists(item)) {
if (this.expectedContents != null) {
try {
return this.expectedContents.matches(read((File) item));
}
catch (IOException e) {
return false;
}
}
return true;
}
return false;
}
private boolean snippetFileExists(Object item) {
return item instanceof File && ((File) item).isFile();
}
private String read(File snippetFile) throws IOException {
return FileCopyUtils.copyToString(new FileReader(snippetFile));
}
@Override
public void describeMismatch(Object item, Description description) {
if (!snippetFileExists(item)) {
description.appendText("The file " + item + " does not exist");
}
else if (this.expectedContents != null) {
try {
this.expectedContents
.describeMismatch(read((File) item), description);
}
catch (IOException e) {
description.appendText("The contents of " + item
+ " cound not be read");
}
}
}
@Override
public void describeTo(Description description) {
if (this.expectedContents != null) {
this.expectedContents.describeTo(description);
}
else {
description.appendText("Asciidoctor snippet");
}
}
public SnippetMatcher withContents(Matcher<String> matcher) {
this.expectedContents = matcher;
return this;
}
}
}

View File

@@ -0,0 +1,5 @@
[source,bash]
.{{title}}
----
$ curl {{arguments}}
----

View File

@@ -0,0 +1,9 @@
[source,http]
.{{title}}
----
{{method}} {{path}} HTTP/1.1
{{#headers}}
{{name}}: {{value}}
{{/headers}}
{{requestBody}}
----

View File

@@ -0,0 +1,9 @@
[source,http]
.{{title}}
----
HTTP/1.1 {{statusCode}} {{statusReason}}
{{#headers}}
{{name}}: {{value}}
{{/headers}}
{{responseBody}}
----

View File

@@ -0,0 +1,10 @@
|===
|Relation|Description|Foo
{{#links}}
|{{rel}}
|{{description}}
|{{foo}}
{{/links}}
|===

View File

@@ -0,0 +1,10 @@
.{{title}}
|===
|Relation|Description
{{#links}}
|{{rel}}
|{{description}}
{{/links}}
|===

View File

@@ -0,0 +1,10 @@
|===
|Parameter|Description|Foo
{{#parameters}}
|{{name}}
|{{description}}
|{{foo}}
{{/parameters}}
|===

View File

@@ -0,0 +1,10 @@
.{{title}}
|===
|Parameter|Description
{{#parameters}}
|{{name}}
|{{description}}
{{/parameters}}
|===

View File

@@ -0,0 +1,11 @@
|===
|Path|Type|Description|Foo
{{#fields}}
|{{path}}
|{{type}}
|{{description}}
|{{foo}}
{{/fields}}
|===

View File

@@ -0,0 +1,11 @@
.{{title}}
|===
|Path|Type|Description
{{#fields}}
|{{path}}
|{{type}}
|{{description}}
{{/fields}}
|===

View File

@@ -0,0 +1,10 @@
|===
|Parameter|Description|Foo
{{#parameters}}
|{{name}}
|{{description}}
|{{foo}}
{{/parameters}}
|===

View File

@@ -0,0 +1,10 @@
.{{title}}
|===
|Parameter|Description
{{#parameters}}
|{{name}}
|{{description}}
{{/parameters}}
|===

View File

@@ -0,0 +1,11 @@
|===
|Path|Type|Description|Foo
{{#fields}}
|{{path}}
|{{type}}
|{{description}}
|{{foo}}
{{/fields}}
|===

View File

@@ -0,0 +1,11 @@
.{{title}}
|===
|Path|Type|Description
{{#fields}}
|{{path}}
|{{type}}
|{{description}}
{{/fields}}
|===

View File

@@ -0,0 +1,9 @@
{
"_links": {
"alpha": "http://alpha.example.com",
"bravo": "http://bravo.example.com"
},
"_embedded": "embedded-test",
"beta": "beta-value",
"charlie": "charlie-value"
}

View File

@@ -0,0 +1,5 @@
{
"_embedded": "embedded-test",
"beta": "beta-value",
"charlie": "charlie-value"
}

View File

@@ -0,0 +1,8 @@
{
"_links": {
"alpha": "http://alpha.example.com",
"bravo": "http://bravo.example.com"
},
"beta": "beta-value",
"charlie": "charlie-value"
}

View File

@@ -0,0 +1,19 @@
{
"alpha": "alpha-value",
"bravo": 123,
"charlie": {
"one": 456,
"two": "two-value"
},
"delta": [
"delta-value-1",
"delta-value-2"
],
"echo": [{
"one": 789,
"two": "two-value"
},{
"one": 987,
"two": "value-two"
}]
}

View File

@@ -0,0 +1,3 @@
{
"alpha": "alpha-value"
}

View File

@@ -0,0 +1,9 @@
{
"links": [ {
"rel": "alpha",
"href": "http://alpha.example.com"
}, {
"rel": "bravo",
"href": "http://bravo.example.com"
} ]
}

View File

@@ -0,0 +1,9 @@
{
"links": [ {
"rel": "alpha",
"href": "http://alpha.example.com/one"
}, {
"rel": "alpha",
"href": "http://alpha.example.com/two"
} ]
}

View File

@@ -0,0 +1,6 @@
{
"links": [ {
"rel": "alpha",
"href": "http://alpha.example.com"
} ]
}

View File

@@ -0,0 +1,5 @@
{
"_links": {
"alpha": ["http://alpha.example.com/one", "http://alpha.example.com/two"]
}
}

View File

@@ -0,0 +1,6 @@
{
"_links": {
"alpha": "http://alpha.example.com",
"bravo": "http://bravo.example.com"
}
}

View File

@@ -0,0 +1,5 @@
{
"_links": {
"alpha": ["http://alpha.example.com/one", "http://alpha.example.com/two"]
}
}

View File

@@ -0,0 +1,5 @@
{
"_links": {
"alpha": "http://alpha.example.com"
}
}

View File

@@ -0,0 +1,9 @@
{
"_links": [ {
"rel": "alpha",
"href": "http://alpha.example.com/one"
}, {
"rel": "alpha",
"href": "http://alpha.example.com/two"
} ]
}

View File

@@ -0,0 +1 @@
javax.validation.constraints.NotNull.description=Should not be null