Add support for generating snippets in Markdown

This commit introduces support for generating snippets formatted
using Markdown. Asciidoctor remains the default.

A new SnippetFormat abstraction has been introduced with Asciidoctor
and Markdown implementations provided out of the box.
Markdown-formatted templates are also provided for all of the default
snippets.

Please refer to the updated reference documentation for further
details.

Closes gh-150
Closes gh-19
This commit is contained in:
Andy Wilkinson
2016-01-29 16:10:00 +00:00
parent b34d2dd443
commit 4d37dea1d6
99 changed files with 2100 additions and 1037 deletions

View File

@@ -93,13 +93,20 @@ public abstract class RestDocumentationConfigurer<S, T> {
private static final class TemplateEngineConfigurer extends AbstractConfigurer {
private TemplateEngine templateEngine = new MustacheTemplateEngine(
new StandardTemplateResourceResolver());
private TemplateEngine templateEngine;
@Override
public void apply(Map<String, Object> configuration,
RestDocumentationContext context) {
configuration.put(TemplateEngine.class.getName(), this.templateEngine);
TemplateEngine engineToUse = this.templateEngine;
if (engineToUse == null) {
SnippetConfiguration snippetConfiguration = (SnippetConfiguration) configuration
.get(SnippetConfiguration.class.getName());
engineToUse = new MustacheTemplateEngine(
new StandardTemplateResourceResolver(
snippetConfiguration.getFormat()));
}
configuration.put(TemplateEngine.class.getName(), engineToUse);
}
private void setTemplateEngine(TemplateEngine templateEngine) {
@@ -117,8 +124,12 @@ public abstract class RestDocumentationConfigurer<S, T> {
RestDocumentationContext context) {
WriterResolver resolverToUse = this.writerResolver;
if (resolverToUse == null) {
SnippetConfiguration snippetConfiguration = (SnippetConfiguration) configuration
.get(SnippetConfiguration.class.getName());
resolverToUse = new StandardWriterResolver(
new RestDocumentationContextPlaceholderResolver(context));
new RestDocumentationContextPlaceholderResolver(context),
snippetConfiguration.getEncoding(),
snippetConfiguration.getFormat());
}
configuration.put(WriterResolver.class.getName(), resolverToUse);
}

View File

@@ -0,0 +1,45 @@
/*
* Copyright 2014-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.restdocs.config;
import org.springframework.restdocs.snippet.SnippetFormat;
/**
* An encapsulation of the configuration for documentation snippets.
*
* @author Andy Wilkinson
*/
class SnippetConfiguration {
private final String encoding;
private final SnippetFormat format;
SnippetConfiguration(String encoding, SnippetFormat format) {
this.encoding = encoding;
this.format = format;
}
String getEncoding() {
return this.encoding;
}
SnippetFormat getFormat() {
return this.format;
}
}

View File

@@ -24,7 +24,8 @@ import org.springframework.restdocs.RestDocumentationContext;
import org.springframework.restdocs.curl.CurlDocumentation;
import org.springframework.restdocs.http.HttpDocumentation;
import org.springframework.restdocs.snippet.Snippet;
import org.springframework.restdocs.snippet.WriterResolver;
import org.springframework.restdocs.snippet.SnippetFormat;
import org.springframework.restdocs.snippet.SnippetFormats;
/**
* A configurer that can be used to configure the generated documentation snippets.
@@ -51,8 +52,18 @@ public abstract class SnippetConfigurer<P, T> extends AbstractNestedConfigurer<P
*/
public static final String DEFAULT_SNIPPET_ENCODING = "UTF-8";
/**
* The default format for documentation snippets.
*
* @see #withFormat(SnippetFormat)
*/
public static final SnippetFormat DEFAULT_SNIPPET_FORMAT = SnippetFormats
.asciidoctor();
private String snippetEncoding = DEFAULT_SNIPPET_ENCODING;
private SnippetFormat snippetFormat = DEFAULT_SNIPPET_FORMAT;
/**
* Creates a new {@code SnippetConfigurer} with the given {@code parent}.
*
@@ -64,8 +75,8 @@ public abstract class SnippetConfigurer<P, T> extends AbstractNestedConfigurer<P
@Override
public void apply(Map<String, Object> configuration, RestDocumentationContext context) {
((WriterResolver) configuration.get(WriterResolver.class.getName()))
.setEncoding(this.snippetEncoding);
configuration.put(SnippetConfiguration.class.getName(), new SnippetConfiguration(
this.snippetEncoding, this.snippetFormat));
configuration.put(ATTRIBUTE_DEFAULT_SNIPPETS, this.defaultSnippets);
}
@@ -93,4 +104,17 @@ public abstract class SnippetConfigurer<P, T> extends AbstractNestedConfigurer<P
this.defaultSnippets = Arrays.asList(defaultSnippets);
return (T) this;
}
/**
* Configures the format of the documentation snippets.
*
* @param format the snippet format
* @return {@code this}
*/
@SuppressWarnings("unchecked")
public T withFormat(SnippetFormat format) {
this.snippetFormat = format;
return (T) this;
}
}

View File

@@ -0,0 +1,34 @@
/*
* Copyright 2014-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* 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;
/**
* A {@link SnippetFormat} provides information about a particular snippet format, such as
* Asciidoctor or Markdown.
*
* @author Andy Wilkinson
*/
public interface SnippetFormat {
/**
* Returns the snippet format's file extension.
*
* @return the file extension
*/
String getFileExtension();
}

View File

@@ -0,0 +1,74 @@
/*
* Copyright 2014-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* 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;
/**
* An enumeration of the built-in snippet formats.
*
* @author Andy Wilkinson
*/
public abstract class SnippetFormats {
private static final SnippetFormat ASCIIDOCTOR = new AsciidoctorSnippetFormat();
private static final SnippetFormat MARKDOWN = new MarkdownSnippetFormat();
private SnippetFormats() {
}
/**
* Returns the Asciidoctor snippet format.
*
* @return the snippet format
*/
public static SnippetFormat asciidoctor() {
return ASCIIDOCTOR;
}
/**
* Returns the Markdown snippet format.
*
* @return the snippet format
*/
public static SnippetFormat markdown() {
return MARKDOWN;
}
private static final class AsciidoctorSnippetFormat implements SnippetFormat {
private static final String FILE_EXTENSION = "adoc";
@Override
public String getFileExtension() {
return FILE_EXTENSION;
}
}
private static final class MarkdownSnippetFormat implements SnippetFormat {
private static final String FILE_EXTENSION = "md";
@Override
public String getFileExtension() {
return FILE_EXTENSION;
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2015 the original author or authors.
* Copyright 2014-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -33,29 +33,54 @@ import org.springframework.util.PropertyPlaceholderHelper.PlaceholderResolver;
*/
public final class StandardWriterResolver implements WriterResolver {
private String encoding = "UTF-8";
private final PlaceholderResolver placeholderResolver;
private final PropertyPlaceholderHelper propertyPlaceholderHelper = new PropertyPlaceholderHelper(
"{", "}");
private String encoding = "UTF-8";
private SnippetFormat snippetFormat;
/**
* Creates a new {@code StandardWriterResolver} that will use the given
* {@code placeholderResolver} to resolve any placeholders in the
* {@code operationName}.
* {@code operationName}. Writers will use {@code UTF-8} encoding and, when writing to
* a file, will use a filename appropriate for Asciidoctor content.
*
* @param placeholderResolver the placeholder resolver
* @deprecated since 1.1.0 in favor of
* {@link #StandardWriterResolver(PropertyPlaceholderHelper.PlaceholderResolver, String, SnippetFormat)}
*/
@Deprecated
public StandardWriterResolver(PlaceholderResolver placeholderResolver) {
this(placeholderResolver, "UTF-8", SnippetFormats.asciidoctor());
}
/**
* Creates a new {@code StandardWriterResolver} that will use the given
* {@code placeholderResolver} to resolve any placeholders in the
* {@code operationName}. Writers will use the given {@code encoding} and, when
* writing to a file, will use a filename appropriate for content in the given
* {@code snippetFormat}.
*
* @param placeholderResolver the placeholder resolver
* @param encoding the encoding
* @param snippetFormat the snippet format
*/
public StandardWriterResolver(PlaceholderResolver placeholderResolver,
String encoding, SnippetFormat snippetFormat) {
this.placeholderResolver = placeholderResolver;
this.encoding = encoding;
this.snippetFormat = snippetFormat;
}
@Override
public Writer resolve(String operationName, String snippetName,
RestDocumentationContext context) throws IOException {
File outputFile = resolveFile(this.propertyPlaceholderHelper.replacePlaceholders(
operationName, this.placeholderResolver), snippetName + ".adoc", context);
operationName, this.placeholderResolver), snippetName + "."
+ this.snippetFormat.getFileExtension(), context);
if (outputFile != null) {
createDirectoriesIfNecessary(outputFile);
@@ -95,4 +120,5 @@ public final class StandardWriterResolver implements WriterResolver {
throw new IllegalStateException("Failed to create directory '" + parent + "'");
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2015 the original author or authors.
* Copyright 2014-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -47,7 +47,10 @@ public interface WriterResolver {
* resolver.
*
* @param encoding the encoding
* @deprecated since 1.1.0 in favour of configuring the encoding when to resolver is
* created
*/
@Deprecated
void setEncoding(String encoding);
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2015 the original author or authors.
* Copyright 2014-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,27 +18,54 @@ package org.springframework.restdocs.templates;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.restdocs.snippet.SnippetFormat;
import org.springframework.restdocs.snippet.SnippetFormats;
/**
* Standard implementation of {@link TemplateResourceResolver}.
* <p>
* Templates are resolved by first looking for a resource on the classpath named
* Templates are resolved by looking for a resource on the classpath named
* {@code org/springframework/restdocs/templates/&#123;name&#125;.snippet}. If no such
* resource exists {@code default-} is prepended to the name and the classpath is checked
* again. The built-in snippet templates are all named {@code default- name}, thereby
* allowing them to be overridden.
* resource exists an attempt is made to return a default resource that is appropriate for
* the configured snippet format.
*
* @author Andy Wilkinson
*/
public class StandardTemplateResourceResolver implements TemplateResourceResolver {
private final SnippetFormat snippetFormat;
/**
* Creates a new {@code StandardTemplateResourceResolver} that will produce default
* template resources formatted with Asciidoctor.
*
* @deprecated since 1.1.0 in favour of
* {@link #StandardTemplateResourceResolver(SnippetFormat)}
*/
@Deprecated
public StandardTemplateResourceResolver() {
this(SnippetFormats.asciidoctor());
}
/**
* Creates a new {@code StandardTemplateResourceResolver} that will produce default
* template resources formatted with the given {@code snippetFormat}.
*
* @param snippetFormat the format for the default snippet templates
*/
public StandardTemplateResourceResolver(SnippetFormat snippetFormat) {
this.snippetFormat = snippetFormat;
}
@Override
public Resource resolveTemplateResource(String name) {
ClassPathResource classPathResource = new ClassPathResource(
"org/springframework/restdocs/templates/" + name + ".snippet");
if (!classPathResource.exists()) {
classPathResource = new ClassPathResource(
"org/springframework/restdocs/templates/default-" + name + ".snippet");
"org/springframework/restdocs/templates/"
+ this.snippetFormat.getFileExtension() + "/" + name
+ ".snippet");
if (!classPathResource.exists()) {
throw new IllegalStateException("Template named '" + name
+ "' could not be resolved");

View File

@@ -0,0 +1,3 @@
```bash
$ curl {{url}} {{options}}
```

View File

@@ -0,0 +1,7 @@
```http
{{method}} {{path}} HTTP/1.1
{{#headers}}
{{name}}: {{value}}
{{/headers}}
{{requestBody}}
```

View File

@@ -0,0 +1,7 @@
```http
HTTP/1.1 {{statusCode}} {{statusReason}}
{{#headers}}
{{name}}: {{value}}
{{/headers}}
{{responseBody}}
```

View File

@@ -0,0 +1,5 @@
Relation | Description
-------- | -----------
{{#links}}
{{rel}} | {{description}}
{{/links}}

View File

@@ -0,0 +1,6 @@
{{path}}
Parameter | Description
--------- | -----------
{{#parameters}}
{{name}} | {{description}}
{{/parameters}}

View File

@@ -0,0 +1,5 @@
Path | Type | Description
---- | ---- | -----------
{{#fields}}
{{path}} | {{type}} | {{description}}
{{/fields}}

View File

@@ -0,0 +1,5 @@
Name | Description
---- | -----------
{{#headers}}
{{name}} | {{description}}
{{/headers}}

View File

@@ -0,0 +1,5 @@
Parameter | Description
--------- | -----------
{{#parameters}}
{{name}} | {{description}}
{{/parameters}}

View File

@@ -0,0 +1,5 @@
Path | Type | Description
---- | ---- | -----------
{{#fields}}
{{path}} | {{type}} | {{description}}
{{/fields}}

View File

@@ -0,0 +1,5 @@
Name | Description
---- | -----------
{{#headers}}
{{name}} | {{description}}
{{/headers}}

View File

@@ -0,0 +1,96 @@
/*
* Copyright 2012-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.restdocs;
import java.util.Arrays;
import java.util.List;
import org.junit.Rule;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
import org.springframework.core.io.FileSystemResource;
import org.springframework.http.HttpStatus;
import org.springframework.restdocs.snippet.SnippetFormat;
import org.springframework.restdocs.test.ExpectedSnippet;
import org.springframework.restdocs.test.OperationBuilder;
import org.springframework.restdocs.test.SnippetMatchers;
import org.springframework.restdocs.test.SnippetMatchers.CodeBlockMatcher;
import org.springframework.restdocs.test.SnippetMatchers.HttpRequestMatcher;
import org.springframework.restdocs.test.SnippetMatchers.HttpResponseMatcher;
import org.springframework.restdocs.test.SnippetMatchers.TableMatcher;
import org.springframework.web.bind.annotation.RequestMethod;
import static org.springframework.restdocs.snippet.SnippetFormats.asciidoctor;
import static org.springframework.restdocs.snippet.SnippetFormats.markdown;
/**
* Abstract base class for testing snippet generation.
*
* @author Andy Wilkinson
*/
@RunWith(Parameterized.class)
public abstract class AbstractSnippetTests {
protected final SnippetFormat snippetFormat;
@Rule
public ExpectedSnippet snippet;
@Parameters(name = "{0}")
public static List<Object[]> parameters() {
return Arrays.asList(new Object[] { "Asciidoctor", asciidoctor() }, new Object[] {
"Markdown", markdown() });
}
public AbstractSnippetTests(String name, SnippetFormat snippetFormat) {
this.snippet = new ExpectedSnippet(snippetFormat);
this.snippetFormat = snippetFormat;
}
public CodeBlockMatcher<?> codeBlock(String language) {
return SnippetMatchers.codeBlock(this.snippetFormat, language);
}
public TableMatcher<?> tableWithHeader(String... headers) {
return SnippetMatchers.tableWithHeader(this.snippetFormat, headers);
}
public TableMatcher<?> tableWithTitleAndHeader(String title, String... headers) {
return SnippetMatchers
.tableWithTitleAndHeader(this.snippetFormat, title, headers);
}
public HttpRequestMatcher httpRequest(RequestMethod method, String uri) {
return SnippetMatchers.httpRequest(this.snippetFormat, method, uri);
}
public HttpResponseMatcher httpResponse(HttpStatus responseStatus) {
return SnippetMatchers.httpResponse(this.snippetFormat, responseStatus);
}
public OperationBuilder operationBuilder(String name) {
return new OperationBuilder(name, this.snippet.getOutputDirectory(),
this.snippetFormat);
}
protected FileSystemResource snippetResource(String name) {
return new FileSystemResource("src/test/resources/custom-snippet-templates/"
+ this.snippetFormat.getFileExtension() + "/" + name + ".snippet");
}
}

View File

@@ -18,17 +18,16 @@ package org.springframework.restdocs.curl;
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.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.restdocs.AbstractSnippetTests;
import org.springframework.restdocs.snippet.SnippetFormat;
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.Base64Utils;
import static org.hamcrest.CoreMatchers.containsString;
@@ -36,7 +35,6 @@ import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
import static org.springframework.restdocs.snippet.Attributes.attributes;
import static org.springframework.restdocs.snippet.Attributes.key;
import static org.springframework.restdocs.test.SnippetMatchers.codeBlock;
/**
* Tests for {@link CurlRequestSnippet}.
@@ -47,30 +45,27 @@ import static org.springframework.restdocs.test.SnippetMatchers.codeBlock;
* @author Jonathan Pearlin
* @author Paul-Christian Volkmer
*/
public class CurlRequestSnippetTests {
@RunWith(Parameterized.class)
public class CurlRequestSnippetTests extends AbstractSnippetTests {
@Rule
public ExpectedSnippet snippet = new ExpectedSnippet();
@Rule
public ExpectedException thrown = ExpectedException.none();
public CurlRequestSnippetTests(String name, SnippetFormat snippetFormat) {
super(name, snippetFormat);
}
@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());
new CurlRequestSnippet().document(operationBuilder("get-request").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());
new CurlRequestSnippet().document(operationBuilder("non-get-request")
.request("http://localhost/foo").method("POST").build());
}
@Test
@@ -78,9 +73,8 @@ public class CurlRequestSnippetTests {
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());
new CurlRequestSnippet().document(operationBuilder("request-with-content")
.request("http://localhost/foo").content("content").build());
}
@Test
@@ -89,8 +83,7 @@ public class CurlRequestSnippetTests {
.withContents(
codeBlock("bash").content(
"$ curl 'http://localhost/foo?param=value' -i"));
new CurlRequestSnippet().document(new OperationBuilder(
"request-with-query-string", this.snippet.getOutputDirectory()).request(
new CurlRequestSnippet().document(operationBuilder("request-with-query-string").request(
"http://localhost/foo?param=value").build());
}
@@ -99,8 +92,7 @@ public class CurlRequestSnippetTests {
this.snippet.expectCurlRequest("post-request-with-query-string").withContents(
codeBlock("bash").content(
"$ curl 'http://localhost/foo?param=value' -i -X POST"));
new CurlRequestSnippet().document(new OperationBuilder(
"post-request-with-query-string", this.snippet.getOutputDirectory())
new CurlRequestSnippet().document(operationBuilder("post-request-with-query-string")
.request("http://localhost/foo?param=value").method("POST").build());
}
@@ -110,8 +102,7 @@ public class CurlRequestSnippetTests {
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())
.document(operationBuilder("post-request-with-one-parameter")
.request("http://localhost/foo").method("POST").param("k1", "v1")
.build());
}
@@ -123,10 +114,10 @@ public class CurlRequestSnippetTests {
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());
new CurlRequestSnippet()
.document(operationBuilder("post-request-with-multiple-parameters")
.request("http://localhost/foo").method("POST")
.param("k1", "v1", "v1-bis").param("k2", "v2").build());
}
@Test
@@ -136,10 +127,10 @@ public class CurlRequestSnippetTests {
.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());
new CurlRequestSnippet().document(operationBuilder(
"post-request-with-url-encoded-parameter")
.request("http://localhost/foo").method("POST").param("k1", "a&b")
.build());
}
@Test
@@ -150,10 +141,10 @@ public class CurlRequestSnippetTests {
codeBlock("bash")
.content(
"$ curl 'http://localhost/foo?a=alpha' -i -X POST -d 'b=bravo'"));
new CurlRequestSnippet().document(new OperationBuilder(
"post-request-with-query-string-and-parameter", this.snippet
.getOutputDirectory()).request("http://localhost/foo?a=alpha")
.method("POST").param("b", "bravo").build());
new CurlRequestSnippet().document(operationBuilder(
"post-request-with-query-string-and-parameter")
.request("http://localhost/foo?a=alpha").method("POST")
.param("b", "bravo").build());
}
@Test
@@ -165,10 +156,10 @@ public class CurlRequestSnippetTests {
codeBlock("bash")
.content(
"$ curl 'http://localhost/foo?a=alpha' -i -X POST -d 'b=bravo'"));
new CurlRequestSnippet().document(new OperationBuilder(
"post-request-with-overlapping-query-string-and-parameters", this.snippet
.getOutputDirectory()).request("http://localhost/foo?a=alpha")
.method("POST").param("a", "alpha").param("b", "bravo").build());
new CurlRequestSnippet().document(operationBuilder(
"post-request-with-overlapping-query-string-and-parameters")
.request("http://localhost/foo?a=alpha").method("POST")
.param("a", "alpha").param("b", "bravo").build());
}
@Test
@@ -176,8 +167,7 @@ public class CurlRequestSnippetTests {
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())
new CurlRequestSnippet().document(operationBuilder("put-request-with-one-parameter")
.request("http://localhost/foo").method("PUT").param("k1", "v1").build());
}
@@ -188,11 +178,9 @@ public class CurlRequestSnippetTests {
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());
new CurlRequestSnippet().document(operationBuilder("put-request-with-multiple-parameters")
.request("http://localhost/foo").method("PUT").param("k1", "v1")
.param("k1", "v1-bis").param("k2", "v2").build());
}
@Test
@@ -201,9 +189,8 @@ public class CurlRequestSnippetTests {
.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")
new CurlRequestSnippet().document(operationBuilder(
"put-request-with-url-encoded-parameter").request("http://localhost/foo")
.method("PUT").param("k1", "a&b").build());
}
@@ -213,8 +200,8 @@ public class CurlRequestSnippetTests {
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")
new CurlRequestSnippet().document(operationBuilder("request-with-headers")
.request("http://localhost/foo")
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
.header("a", "alpha").build());
}
@@ -226,8 +213,7 @@ public class CurlRequestSnippetTests {
+ "'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())
new CurlRequestSnippet().document(operationBuilder("multipart-post-no-original-filename")
.request("http://localhost/upload").method("POST")
.header(HttpHeaders.CONTENT_TYPE, MediaType.MULTIPART_FORM_DATA_VALUE)
.part("metadata", "{\"description\": \"foo\"}".getBytes()).build());
@@ -240,8 +226,7 @@ public class CurlRequestSnippetTests {
+ "'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())
new CurlRequestSnippet().document(operationBuilder("multipart-post-with-content-type")
.request("http://localhost/upload").method("POST")
.header(HttpHeaders.CONTENT_TYPE, MediaType.MULTIPART_FORM_DATA_VALUE)
.part("image", new byte[0])
@@ -256,9 +241,8 @@ public class CurlRequestSnippetTests {
+ "'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")
new CurlRequestSnippet().document(operationBuilder("multipart-post")
.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());
@@ -272,8 +256,7 @@ public class CurlRequestSnippetTests {
+ "-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())
new CurlRequestSnippet().document(operationBuilder("multipart-post-with-parameters")
.request("http://localhost/upload").method("POST")
.header(HttpHeaders.CONTENT_TYPE, MediaType.MULTIPART_FORM_DATA_VALUE)
.part("image", new byte[0])
@@ -281,34 +264,30 @@ public class CurlRequestSnippetTests {
.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);
given(resolver.resolveTemplateResource("curl-request"))
.willReturn(
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());
}
@Test
public void basicAuthCredentialsAreSuppliedUsingUserOption() throws IOException {
this.snippet.expectCurlRequest("basic-auth").withContents(
codeBlock("bash").content(
"$ curl 'http://localhost/foo' -i -u 'user:secret'"));
new CurlRequestSnippet().document(new OperationBuilder("basic-auth", this.snippet
.getOutputDirectory())
new CurlRequestSnippet().document(operationBuilder("basic-auth")
.request("http://localhost/foo")
.header(HttpHeaders.AUTHORIZATION,
"Basic " + Base64Utils.encodeToString("user:secret".getBytes()))
.build());
}
@Test
public void customAttributes() throws IOException {
this.snippet.expectCurlRequest("custom-attributes").withContents(
containsString("curl request title"));
TemplateResourceResolver resolver = mock(TemplateResourceResolver.class);
given(resolver.resolveTemplateResource("curl-request")).willReturn(
snippetResource("curl-request-with-title"));
new CurlRequestSnippet(attributes(key("title").value("curl request title")))
.document(operationBuilder("custom-attributes")
.attribute(TemplateEngine.class.getName(),
new MustacheTemplateEngine(resolver))
.request("http://localhost/foo").build());
}
}

View File

@@ -0,0 +1,72 @@
/*
* Copyright 2012-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* 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.headers;
import java.io.IOException;
import java.util.Arrays;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.springframework.restdocs.snippet.SnippetException;
import org.springframework.restdocs.snippet.SnippetFormats;
import org.springframework.restdocs.test.ExpectedSnippet;
import org.springframework.restdocs.test.OperationBuilder;
import static org.hamcrest.CoreMatchers.endsWith;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.springframework.restdocs.headers.HeaderDocumentation.headerWithName;
/**
* Tests for failures when rendering {@link RequestHeadersSnippet} due to missing or
* undocumented headers.
*
* @author Andy Wilkinson
*/
public class RequestHeadersSnippetFailureTests {
@Rule
public ExpectedSnippet snippet = new ExpectedSnippet(SnippetFormats.asciidoctor());
@Rule
public ExpectedException thrown = ExpectedException.none();
@Test
public void missingRequestHeader() throws IOException {
this.thrown.expect(SnippetException.class);
this.thrown
.expectMessage(equalTo("Headers with the following names were not found"
+ " in the request: [Accept]"));
new RequestHeadersSnippet(Arrays.asList(headerWithName("Accept").description(
"one"))).document(new OperationBuilder("missing-request-headers",
this.snippet.getOutputDirectory()).request("http://localhost").build());
}
@Test
public void undocumentedRequestHeaderAndMissingRequestHeader() throws IOException {
this.thrown.expect(SnippetException.class);
this.thrown
.expectMessage(endsWith("Headers with the following names were not found"
+ " in the request: [Accept]"));
new RequestHeadersSnippet(Arrays.asList(headerWithName("Accept").description(
"one"))).document(new OperationBuilder(
"undocumented-request-header-and-missing-request-header", this.snippet
.getOutputDirectory()).request("http://localhost")
.header("X-Test", "test").build());
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2015 the original author or authors.
* Copyright 2014-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -19,26 +19,20 @@ package org.springframework.restdocs.headers;
import java.io.IOException;
import java.util.Arrays;
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.AbstractSnippetTests;
import org.springframework.restdocs.snippet.SnippetFormat;
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 static org.hamcrest.CoreMatchers.endsWith;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.startsWith;
import static org.hamcrest.CoreMatchers.containsString;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
import static org.springframework.restdocs.headers.HeaderDocumentation.headerWithName;
import static org.springframework.restdocs.snippet.Attributes.attributes;
import static org.springframework.restdocs.snippet.Attributes.key;
import static org.springframework.restdocs.test.SnippetMatchers.tableWithHeader;
/**
* Tests for {@link RequestHeadersSnippet}.
@@ -46,13 +40,11 @@ import static org.springframework.restdocs.test.SnippetMatchers.tableWithHeader;
* @author Andreas Evers
* @author Andy Wilkinson
*/
public class RequestHeadersSnippetTests {
public class RequestHeadersSnippetTests extends AbstractSnippetTests {
@Rule
public final ExpectedException thrown = ExpectedException.none();
@Rule
public final ExpectedSnippet snippet = new ExpectedSnippet();
public RequestHeadersSnippetTests(String name, SnippetFormat snippetFormat) {
super(name, snippetFormat);
}
@Test
public void requestWithHeaders() throws IOException {
@@ -67,14 +59,13 @@ public class RequestHeadersSnippetTests {
.description("three"), headerWithName("Accept-Language")
.description("four"), headerWithName("Cache-Control")
.description("five"),
headerWithName("Connection").description("six")))
.document(new OperationBuilder("request-with-headers", this.snippet
.getOutputDirectory()).request("http://localhost")
.header("X-Test", "test").header("Accept", "*/*")
.header("Accept-Encoding", "gzip, deflate")
.header("Accept-Language", "en-US,en;q=0.5")
.header("Cache-Control", "max-age=0")
.header("Connection", "keep-alive").build());
headerWithName("Connection").description("six"))).document(operationBuilder(
"request-with-headers").request("http://localhost")
.header("X-Test", "test").header("Accept", "*/*")
.header("Accept-Encoding", "gzip, deflate")
.header("Accept-Language", "en-US,en;q=0.5")
.header("Cache-Control", "max-age=0").header("Connection", "keep-alive")
.build());
}
@Test
@@ -83,8 +74,7 @@ public class RequestHeadersSnippetTests {
.expectRequestHeaders("case-insensitive-request-headers")
.withContents(tableWithHeader("Name", "Description").row("X-Test", "one"));
new RequestHeadersSnippet(Arrays.asList(headerWithName("X-Test").description(
"one"))).document(new OperationBuilder(
"case-insensitive-request-headers", this.snippet.getOutputDirectory())
"one"))).document(operationBuilder("case-insensitive-request-headers")
.request("/").header("X-test", "test").build());
}
@@ -97,37 +87,28 @@ public class RequestHeadersSnippetTests {
}
@Test
public void missingRequestHeader() throws IOException {
this.thrown.expect(SnippetException.class);
this.thrown
.expectMessage(equalTo("Headers with the following names were not found"
+ " in the request: [Accept]"));
new RequestHeadersSnippet(Arrays.asList(headerWithName("Accept").description(
"one"))).document(new OperationBuilder("missing-request-headers",
this.snippet.getOutputDirectory()).request("http://localhost").build());
}
@Test
public void undocumentedRequestHeaderAndMissingRequestHeader() throws IOException {
this.thrown.expect(SnippetException.class);
this.thrown
.expectMessage(endsWith("Headers with the following names were not found"
+ " in the request: [Accept]"));
new RequestHeadersSnippet(Arrays.asList(headerWithName("Accept").description(
"one"))).document(new OperationBuilder(
"undocumented-request-header-and-missing-request-header", this.snippet
.getOutputDirectory()).request("http://localhost")
.header("X-Test", "test").build());
public void requestHeadersWithCustomAttributes() throws IOException {
this.snippet.expectRequestHeaders("request-headers-with-custom-attributes")
.withContents(containsString("Custom title"));
TemplateResourceResolver resolver = mock(TemplateResourceResolver.class);
given(resolver.resolveTemplateResource("request-headers")).willReturn(
snippetResource("request-headers-with-title"));
new RequestHeadersSnippet(Arrays.asList(headerWithName("X-Test").description(
"one")), attributes(key("title").value("Custom title")))
.document(operationBuilder("request-headers-with-custom-attributes")
.attribute(TemplateEngine.class.getName(),
new MustacheTemplateEngine(resolver))
.request("http://localhost").header("X-Test", "test").build());
}
@Test
public void requestHeadersWithCustomDescriptorAttributes() throws IOException {
this.snippet.expectRequestHeaders("request-headers-with-custom-attributes")
.withContents(//
tableWithHeader("Name", "Description", "Foo")
.row("X-Test", "one", "alpha")
.row("Accept-Encoding", "two", "bravo")
.row("Accept", "three", "charlie"));
this.snippet.expectRequestHeaders(
"request-headers-with-custom-descriptor-attributes").withContents(//
tableWithHeader("Name", "Description", "Foo")
.row("X-Test", "one", "alpha")
.row("Accept-Encoding", "two", "bravo")
.row("Accept", "three", "charlie"));
TemplateResourceResolver resolver = mock(TemplateResourceResolver.class);
given(resolver.resolveTemplateResource("request-headers")).willReturn(
snippetResource("request-headers-with-extra-column"));
@@ -137,34 +118,12 @@ public class RequestHeadersSnippetTests {
headerWithName("Accept-Encoding").description("two").attributes(
key("foo").value("bravo")),
headerWithName("Accept").description("three").attributes(
key("foo").value("charlie")))).document(new OperationBuilder(
"request-headers-with-custom-attributes", this.snippet
.getOutputDirectory())
key("foo").value("charlie")))).document(operationBuilder(
"request-headers-with-custom-descriptor-attributes")
.attribute(TemplateEngine.class.getName(),
new MustacheTemplateEngine(resolver)).request("http://localhost")
.header("X-Test", "test").header("Accept-Encoding", "gzip, deflate")
.header("Accept", "*/*").build());
}
@Test
public void requestHeadersWithCustomAttributes() throws IOException {
this.snippet.expectRequestHeaders("request-headers-with-custom-attributes")
.withContents(startsWith(".Custom title"));
TemplateResourceResolver resolver = mock(TemplateResourceResolver.class);
given(resolver.resolveTemplateResource("request-headers")).willReturn(
snippetResource("request-headers-with-title"));
new RequestHeadersSnippet(Arrays.asList(headerWithName("X-Test").description(
"one")), attributes(key("title").value("Custom title")))
.document(new OperationBuilder("request-headers-with-custom-attributes",
this.snippet.getOutputDirectory())
.attribute(TemplateEngine.class.getName(),
new MustacheTemplateEngine(resolver))
.request("http://localhost").header("X-Test", "test").build());
}
private FileSystemResource snippetResource(String name) {
return new FileSystemResource("src/test/resources/custom-snippet-templates/"
+ name + ".snippet");
}
}

View File

@@ -0,0 +1,73 @@
/*
* Copyright 2012-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* 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.headers;
import java.io.IOException;
import java.util.Arrays;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.springframework.restdocs.snippet.SnippetException;
import org.springframework.restdocs.snippet.SnippetFormats;
import org.springframework.restdocs.test.ExpectedSnippet;
import org.springframework.restdocs.test.OperationBuilder;
import static org.hamcrest.CoreMatchers.endsWith;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.springframework.restdocs.headers.HeaderDocumentation.headerWithName;
/**
* Tests for failures when rendering {@link ResponseHeadersSnippet} due to missing or
* undocumented headers.
*
* @author Andy Wilkinson
*/
public class ResponseHeadersSnippetFailureTests {
@Rule
public ExpectedSnippet snippet = new ExpectedSnippet(SnippetFormats.asciidoctor());
@Rule
public ExpectedException thrown = ExpectedException.none();
@Test
public void missingResponseHeader() throws IOException {
this.thrown.expect(SnippetException.class);
this.thrown
.expectMessage(equalTo("Headers with the following names were not found"
+ " in the response: [Content-Type]"));
new ResponseHeadersSnippet(Arrays.asList(headerWithName("Content-Type")
.description("one"))).document(new OperationBuilder(
"missing-response-headers", this.snippet.getOutputDirectory()).response()
.build());
}
@Test
public void undocumentedResponseHeaderAndMissingResponseHeader() throws IOException {
this.thrown.expect(SnippetException.class);
this.thrown
.expectMessage(endsWith("Headers with the following names were not found"
+ " in the response: [Content-Type]"));
new ResponseHeadersSnippet(Arrays.asList(headerWithName("Content-Type")
.description("one"))).document(new OperationBuilder(
"undocumented-response-header-and-missing-response-header", this.snippet
.getOutputDirectory()).response().header("X-Test", "test")
.build());
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2015 the original author or authors.
* Copyright 2014-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -19,26 +19,20 @@ package org.springframework.restdocs.headers;
import java.io.IOException;
import java.util.Arrays;
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.AbstractSnippetTests;
import org.springframework.restdocs.snippet.SnippetFormat;
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 static org.hamcrest.CoreMatchers.endsWith;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.startsWith;
import static org.hamcrest.CoreMatchers.containsString;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
import static org.springframework.restdocs.headers.HeaderDocumentation.headerWithName;
import static org.springframework.restdocs.snippet.Attributes.attributes;
import static org.springframework.restdocs.snippet.Attributes.key;
import static org.springframework.restdocs.test.SnippetMatchers.tableWithHeader;
/**
* Tests for {@link ResponseHeadersSnippet}.
@@ -46,13 +40,11 @@ import static org.springframework.restdocs.test.SnippetMatchers.tableWithHeader;
* @author Andreas Evers
* @author Andy Wilkinson
*/
public class ResponseHeadersSnippetTests {
public class ResponseHeadersSnippetTests extends AbstractSnippetTests {
@Rule
public final ExpectedException thrown = ExpectedException.none();
@Rule
public final ExpectedSnippet snippet = new ExpectedSnippet();
public ResponseHeadersSnippetTests(String name, SnippetFormat snippetFormat) {
super(name, snippetFormat);
}
@Test
public void responseWithHeaders() throws IOException {
@@ -65,8 +57,7 @@ public class ResponseHeadersSnippetTests {
headerWithName("Content-Type").description("two"), headerWithName("Etag")
.description("three"), headerWithName("Cache-Control")
.description("five"), headerWithName("Vary").description("six")))
.document(new OperationBuilder("response-headers", this.snippet
.getOutputDirectory()).response().header("X-Test", "test")
.document(operationBuilder("response-headers").response().header("X-Test", "test")
.header("Content-Type", "application/json")
.header("Etag", "lskjadldj3ii32l2ij23")
.header("Cache-Control", "max-age=0")
@@ -79,9 +70,31 @@ public class ResponseHeadersSnippetTests {
.expectResponseHeaders("case-insensitive-response-headers")
.withContents(tableWithHeader("Name", "Description").row("X-Test", "one"));
new ResponseHeadersSnippet(Arrays.asList(headerWithName("X-Test").description(
"one"))).document(new OperationBuilder(
"case-insensitive-response-headers", this.snippet.getOutputDirectory())
.response().header("X-test", "test").build());
"one"))).document(operationBuilder("case-insensitive-response-headers").response()
.header("X-test", "test").build());
}
@Test
public void undocumentedResponseHeader() throws IOException {
new ResponseHeadersSnippet(Arrays.asList(headerWithName("X-Test").description(
"one"))).document(new OperationBuilder("undocumented-response-header",
this.snippet.getOutputDirectory()).response().header("X-Test", "test")
.header("Content-Type", "*/*").build());
}
@Test
public void responseHeadersWithCustomAttributes() throws IOException {
this.snippet.expectResponseHeaders("response-headers-with-custom-attributes")
.withContents(containsString("Custom title"));
TemplateResourceResolver resolver = mock(TemplateResourceResolver.class);
given(resolver.resolveTemplateResource("response-headers")).willReturn(
snippetResource("response-headers-with-title"));
new ResponseHeadersSnippet(Arrays.asList(headerWithName("X-Test").description(
"one")), attributes(key("title").value("Custom title")))
.document(operationBuilder("response-headers-with-custom-attributes")
.attribute(TemplateEngine.class.getName(),
new MustacheTemplateEngine(resolver)).response()
.header("X-Test", "test").build());
}
@Test
@@ -101,67 +114,12 @@ public class ResponseHeadersSnippetTests {
headerWithName("Content-Type").description("two").attributes(
key("foo").value("bravo")),
headerWithName("Etag").description("three").attributes(
key("foo").value("charlie")))).document(new OperationBuilder(
"response-headers-with-custom-attributes", this.snippet
.getOutputDirectory())
key("foo").value("charlie")))).document(operationBuilder(
"response-headers-with-custom-attributes")
.attribute(TemplateEngine.class.getName(),
new MustacheTemplateEngine(resolver)).response()
.header("X-Test", "test").header("Content-Type", "application/json")
.header("Etag", "lskjadldj3ii32l2ij23").build());
}
@Test
public void responseHeadersWithCustomAttributes() throws IOException {
this.snippet.expectResponseHeaders("response-headers-with-custom-attributes")
.withContents(startsWith(".Custom title"));
TemplateResourceResolver resolver = mock(TemplateResourceResolver.class);
given(resolver.resolveTemplateResource("response-headers")).willReturn(
snippetResource("response-headers-with-title"));
new ResponseHeadersSnippet(Arrays.asList(headerWithName("X-Test").description(
"one")), attributes(key("title").value("Custom title")))
.document(new OperationBuilder("response-headers-with-custom-attributes",
this.snippet.getOutputDirectory())
.attribute(TemplateEngine.class.getName(),
new MustacheTemplateEngine(resolver)).response()
.header("X-Test", "test").build());
}
@Test
public void undocumentedResponseHeader() throws IOException {
new ResponseHeadersSnippet(Arrays.asList(headerWithName("X-Test").description(
"one"))).document(new OperationBuilder("undocumented-response-header",
this.snippet.getOutputDirectory()).response().header("X-Test", "test")
.header("Content-Type", "*/*").build());
}
@Test
public void missingResponseHeader() throws IOException {
this.thrown.expect(SnippetException.class);
this.thrown
.expectMessage(equalTo("Headers with the following names were not found"
+ " in the response: [Content-Type]"));
new ResponseHeadersSnippet(Arrays.asList(headerWithName("Content-Type")
.description("one"))).document(new OperationBuilder(
"missing-response-headers", this.snippet.getOutputDirectory()).response()
.build());
}
@Test
public void undocumentedResponseHeaderAndMissingResponseHeader() throws IOException {
this.thrown.expect(SnippetException.class);
this.thrown
.expectMessage(endsWith("Headers with the following names were not found"
+ " in the response: [Content-Type]"));
new ResponseHeadersSnippet(Arrays.asList(headerWithName("Content-Type")
.description("one"))).document(new OperationBuilder(
"undocumented-response-header-and-missing-response-header", this.snippet
.getOutputDirectory()).response().header("X-Test", "test")
.build());
}
private FileSystemResource snippetResource(String name) {
return new FileSystemResource("src/test/resources/custom-snippet-templates/"
+ name + ".snippet");
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2015 the original author or authors.
* Copyright 2014-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,16 +18,14 @@ package org.springframework.restdocs.http;
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.AbstractSnippetTests;
import org.springframework.restdocs.snippet.SnippetFormat;
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.web.bind.annotation.RequestMethod;
import static org.hamcrest.CoreMatchers.containsString;
@@ -35,21 +33,20 @@ import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
import static org.springframework.restdocs.snippet.Attributes.attributes;
import static org.springframework.restdocs.snippet.Attributes.key;
import static org.springframework.restdocs.test.SnippetMatchers.httpRequest;
/**
* Tests for {@link HttpRequestSnippet}.
*
* @author Andy Wilkinson
* @author Jonathan Pearlin
*
*/
public class HttpRequestSnippetTests {
public class HttpRequestSnippetTests extends AbstractSnippetTests {
private static final String BOUNDARY = "6o2knFse3p53ty9dmcQvWAIx1zInP11uCfbm";
@Rule
public final ExpectedSnippet snippet = new ExpectedSnippet();
public HttpRequestSnippetTests(String name, SnippetFormat snippetFormat) {
super(name, snippetFormat);
}
@Test
public void getRequest() throws IOException {
@@ -57,9 +54,8 @@ public class HttpRequestSnippetTests {
httpRequest(RequestMethod.GET, "/foo").header("Alpha", "a").header(
HttpHeaders.HOST, "localhost"));
new HttpRequestSnippet().document(new OperationBuilder("get-request",
this.snippet.getOutputDirectory()).request("http://localhost/foo")
.header("Alpha", "a").build());
new HttpRequestSnippet().document(operationBuilder("get-request")
.request("http://localhost/foo").header("Alpha", "a").build());
}
@Test
@@ -68,8 +64,7 @@ public class HttpRequestSnippetTests {
httpRequest(RequestMethod.GET, "/foo?bar=baz").header(HttpHeaders.HOST,
"localhost"));
new HttpRequestSnippet().document(new OperationBuilder(
"get-request-with-query-string", this.snippet.getOutputDirectory())
new HttpRequestSnippet().document(operationBuilder("get-request-with-query-string")
.request("http://localhost/foo?bar=baz").build());
}
@@ -81,8 +76,7 @@ public class HttpRequestSnippetTests {
.header(HttpHeaders.HOST, "localhost").content(content)
.header(HttpHeaders.CONTENT_LENGTH, content.getBytes().length));
new HttpRequestSnippet().document(new OperationBuilder(
"post-request-with-content", this.snippet.getOutputDirectory())
new HttpRequestSnippet().document(operationBuilder("post-request-with-content")
.request("http://localhost/foo").method("POST").content(content).build());
}
@@ -97,8 +91,7 @@ public class HttpRequestSnippetTests {
.header(HttpHeaders.CONTENT_LENGTH, contentBytes.length)
.content(japaneseContent));
new HttpRequestSnippet().document(new OperationBuilder(
"post-request-with-charset", this.snippet.getOutputDirectory())
new HttpRequestSnippet().document(operationBuilder("post-request-with-charset")
.request("http://localhost/foo").method("POST")
.header("Content-Type", "text/plain;charset=UTF-8").content(contentBytes)
.build());
@@ -112,8 +105,7 @@ public class HttpRequestSnippetTests {
.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())
new HttpRequestSnippet().document(operationBuilder("post-request-with-parameter")
.request("http://localhost/foo").method("POST").param("b&r", "baz")
.param("a", "alpha").build());
}
@@ -126,8 +118,7 @@ public class HttpRequestSnippetTests {
.header(HttpHeaders.HOST, "localhost").content(content)
.header(HttpHeaders.CONTENT_LENGTH, content.getBytes().length));
new HttpRequestSnippet().document(new OperationBuilder(
"put-request-with-content", this.snippet.getOutputDirectory())
new HttpRequestSnippet().document(operationBuilder("put-request-with-content")
.request("http://localhost/foo").method("PUT").content(content).build());
}
@@ -139,8 +130,7 @@ public class HttpRequestSnippetTests {
.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())
new HttpRequestSnippet().document(operationBuilder("put-request-with-parameter")
.request("http://localhost/foo").method("PUT").param("b&r", "baz")
.param("a", "alpha").build());
}
@@ -154,9 +144,8 @@ public class HttpRequestSnippetTests {
.header("Content-Type",
"multipart/form-data; boundary=" + BOUNDARY)
.header(HttpHeaders.HOST, "localhost").content(expectedContent));
new HttpRequestSnippet().document(new OperationBuilder("multipart-post",
this.snippet.getOutputDirectory()).request("http://localhost/upload")
.method("POST")
new HttpRequestSnippet().document(operationBuilder("multipart-post")
.request("http://localhost/upload").method("POST")
.header(HttpHeaders.CONTENT_TYPE, MediaType.MULTIPART_FORM_DATA_VALUE)
.part("image", "<< data >>".getBytes()).build());
}
@@ -177,8 +166,7 @@ public class HttpRequestSnippetTests {
.header("Content-Type",
"multipart/form-data; boundary=" + BOUNDARY)
.header(HttpHeaders.HOST, "localhost").content(expectedContent));
new HttpRequestSnippet().document(new OperationBuilder(
"multipart-post-with-parameters", this.snippet.getOutputDirectory())
new HttpRequestSnippet().document(operationBuilder("multipart-post-with-parameters")
.request("http://localhost/upload").method("POST")
.header(HttpHeaders.CONTENT_TYPE, MediaType.MULTIPART_FORM_DATA_VALUE)
.param("a", "apple", "avocado").param("b", "banana")
@@ -195,8 +183,7 @@ public class HttpRequestSnippetTests {
.header("Content-Type",
"multipart/form-data; boundary=" + BOUNDARY)
.header(HttpHeaders.HOST, "localhost").content(expectedContent));
new HttpRequestSnippet().document(new OperationBuilder(
"multipart-post-with-content-type", this.snippet.getOutputDirectory())
new HttpRequestSnippet().document(operationBuilder("multipart-post-with-content-type")
.request("http://localhost/upload").method("POST")
.header(HttpHeaders.CONTENT_TYPE, MediaType.MULTIPART_FORM_DATA_VALUE)
.part("image", "<< data >>".getBytes())
@@ -208,8 +195,8 @@ public class HttpRequestSnippetTests {
this.snippet.expectHttpRequest("get-request-custom-host").withContents(
httpRequest(RequestMethod.GET, "/foo").header(HttpHeaders.HOST,
"api.example.com"));
new HttpRequestSnippet().document(new OperationBuilder("get-request-custom-host",
this.snippet.getOutputDirectory()).request("http://localhost/foo")
new HttpRequestSnippet().document(operationBuilder("get-request-custom-host")
.request("http://localhost/foo")
.header(HttpHeaders.HOST, "api.example.com").build());
}
@@ -218,13 +205,10 @@ public class HttpRequestSnippetTests {
this.snippet.expectHttpRequest("request-with-snippet-attributes").withContents(
containsString("Title for the request"));
TemplateResourceResolver resolver = mock(TemplateResourceResolver.class);
given(resolver.resolveTemplateResource("http-request"))
.willReturn(
new FileSystemResource(
"src/test/resources/custom-snippet-templates/http-request-with-title.snippet"));
given(resolver.resolveTemplateResource("http-request")).willReturn(
snippetResource("http-request-with-title"));
new HttpRequestSnippet(attributes(key("title").value("Title for the request")))
.document(new OperationBuilder("request-with-snippet-attributes",
this.snippet.getOutputDirectory())
.document(operationBuilder("request-with-snippet-attributes")
.attribute(TemplateEngine.class.getName(),
new MustacheTemplateEngine(resolver))
.request("http://localhost/foo").build());

View File

@@ -18,24 +18,21 @@ package org.springframework.restdocs.http;
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.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.restdocs.AbstractSnippetTests;
import org.springframework.restdocs.snippet.SnippetFormat;
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 static org.hamcrest.CoreMatchers.containsString;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
import static org.springframework.restdocs.snippet.Attributes.attributes;
import static org.springframework.restdocs.snippet.Attributes.key;
import static org.springframework.restdocs.test.SnippetMatchers.httpResponse;
/**
* Tests for {@link HttpResponseSnippet}.
@@ -43,25 +40,24 @@ import static org.springframework.restdocs.test.SnippetMatchers.httpResponse;
* @author Andy Wilkinson
* @author Jonathan Pearlin
*/
public class HttpResponseSnippetTests {
public class HttpResponseSnippetTests extends AbstractSnippetTests {
@Rule
public final ExpectedSnippet snippet = new ExpectedSnippet();
public HttpResponseSnippetTests(String name, SnippetFormat snippetFormat) {
super(name, snippetFormat);
}
@Test
public void basicResponse() throws IOException {
this.snippet.expectHttpResponse("basic-response").withContents(
httpResponse(HttpStatus.OK));
new HttpResponseSnippet().document(new OperationBuilder("basic-response",
this.snippet.getOutputDirectory()).build());
new HttpResponseSnippet().document(operationBuilder("basic-response").build());
}
@Test
public void nonOkResponse() throws IOException {
this.snippet.expectHttpResponse("non-ok-response").withContents(
httpResponse(HttpStatus.BAD_REQUEST));
new HttpResponseSnippet().document(new OperationBuilder("non-ok-response",
this.snippet.getOutputDirectory()).response()
new HttpResponseSnippet().document(operationBuilder("non-ok-response").response()
.status(HttpStatus.BAD_REQUEST.value()).build());
}
@@ -70,8 +66,7 @@ public class HttpResponseSnippetTests {
this.snippet.expectHttpResponse("response-with-headers").withContents(
httpResponse(HttpStatus.OK).header("Content-Type", "application/json")
.header("a", "alpha"));
new HttpResponseSnippet().document(new OperationBuilder("response-with-headers",
this.snippet.getOutputDirectory()).response()
new HttpResponseSnippet().document(operationBuilder("response-with-headers").response()
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
.header("a", "alpha").build());
}
@@ -82,8 +77,8 @@ public class HttpResponseSnippetTests {
this.snippet.expectHttpResponse("response-with-content").withContents(
httpResponse(HttpStatus.OK).content(content).header(
HttpHeaders.CONTENT_LENGTH, content.getBytes().length));
new HttpResponseSnippet().document(new OperationBuilder("response-with-content",
this.snippet.getOutputDirectory()).response().content(content).build());
new HttpResponseSnippet().document(operationBuilder("response-with-content").response()
.content(content).build());
}
@Test
@@ -95,8 +90,7 @@ public class HttpResponseSnippetTests {
.header("Content-Type", "text/plain;charset=UTF-8")
.content(japaneseContent)
.header(HttpHeaders.CONTENT_LENGTH, contentBytes.length));
new HttpResponseSnippet().document(new OperationBuilder("response-with-charset",
this.snippet.getOutputDirectory()).response()
new HttpResponseSnippet().document(operationBuilder("response-with-charset").response()
.header("Content-Type", "text/plain;charset=UTF-8").content(contentBytes)
.build());
}
@@ -106,13 +100,10 @@ public class HttpResponseSnippetTests {
this.snippet.expectHttpResponse("response-with-snippet-attributes").withContents(
containsString("Title for the response"));
TemplateResourceResolver resolver = mock(TemplateResourceResolver.class);
given(resolver.resolveTemplateResource("http-response"))
.willReturn(
new FileSystemResource(
"src/test/resources/custom-snippet-templates/http-response-with-title.snippet"));
given(resolver.resolveTemplateResource("http-response")).willReturn(
snippetResource("http-response-with-title"));
new HttpResponseSnippet(attributes(key("title").value("Title for the response")))
.document(new OperationBuilder("response-with-snippet-attributes",
this.snippet.getOutputDirectory()).attribute(
.document(operationBuilder("response-with-snippet-attributes").attribute(
TemplateEngine.class.getName(),
new MustacheTemplateEngine(resolver)).build());
}

View File

@@ -0,0 +1,79 @@
/*
* Copyright 2012-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* 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 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.restdocs.snippet.SnippetException;
import org.springframework.restdocs.snippet.SnippetFormats;
import org.springframework.restdocs.test.ExpectedSnippet;
import org.springframework.restdocs.test.OperationBuilder;
import static org.hamcrest.CoreMatchers.equalTo;
/**
* Tests for failures when rendering {@link LinksSnippet} due to missing or undocumented
* links.
*
* @author Andy Wilkinson
*/
public class LinksSnippetFailureTests {
@Rule
public ExpectedSnippet snippet = new ExpectedSnippet(SnippetFormats.asciidoctor());
@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 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());
}
}

View File

@@ -18,51 +18,29 @@ package org.springframework.restdocs.hypermedia;
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.AbstractSnippetTests;
import org.springframework.restdocs.snippet.SnippetFormat;
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;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.startsWith;
import static org.hamcrest.CoreMatchers.containsString;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
import static org.springframework.restdocs.snippet.Attributes.attributes;
import static org.springframework.restdocs.snippet.Attributes.key;
import static org.springframework.restdocs.test.SnippetMatchers.tableWithHeader;
/**
* Tests for {@link LinksSnippet}.
*
* @author Andy Wilkinson
*/
public class LinksSnippetTests {
public class LinksSnippetTests extends AbstractSnippetTests {
@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());
public LinksSnippetTests(String name, SnippetFormat snippetFormat) {
super(name, snippetFormat);
}
@Test
@@ -71,19 +49,8 @@ public class LinksSnippetTests {
tableWithHeader("Relation", "Description").row("b", "Link b"));
new LinksSnippet(new StubLinkExtractor().withLinks(new Link("a", "alpha"),
new Link("b", "bravo")), Arrays.asList(new LinkDescriptor("a").ignored(),
new LinkDescriptor("b").description("Link b")))
.document(new OperationBuilder("ignored-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());
new LinkDescriptor("b").description("Link b"))).document(operationBuilder(
"ignored-link").build());
}
@Test
@@ -92,8 +59,7 @@ public class LinksSnippetTests {
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());
.document(operationBuilder("documented-optional-link").build());
}
@Test
@@ -101,20 +67,8 @@ public class LinksSnippetTests {
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());
.description("bar").optional()))
.document(operationBuilder("missing-optional-link").build());
}
@Test
@@ -125,18 +79,32 @@ public class LinksSnippetTests {
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());
new LinkDescriptor("b").description("two"))).document(operationBuilder(
"documented-links").build());
}
@Test
public void linksWithCustomAttributes() throws IOException {
TemplateResourceResolver resolver = mock(TemplateResourceResolver.class);
given(resolver.resolveTemplateResource("links")).willReturn(
snippetResource("links-with-title"));
this.snippet.expectLinks("links-with-custom-attributes").withContents(
containsString("Title for the links"));
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")), attributes(key("title")
.value("Title for the links"))).document(operationBuilder(
"links-with-custom-attributes").attribute(TemplateEngine.class.getName(),
new MustacheTemplateEngine(resolver)).build());
}
@Test
public void linksWithCustomDescriptorAttributes() throws IOException {
TemplateResourceResolver resolver = mock(TemplateResourceResolver.class);
given(resolver.resolveTemplateResource("links"))
.willReturn(
new FileSystemResource(
"src/test/resources/custom-snippet-templates/links-with-extra-column.snippet"));
given(resolver.resolveTemplateResource("links")).willReturn(
snippetResource("links-with-extra-column"));
this.snippet.expectLinks("links-with-custom-descriptor-attributes").withContents(
tableWithHeader("Relation", "Description", "Foo")
.row("a", "one", "alpha").row("b", "two", "bravo"));
@@ -146,49 +114,10 @@ public class LinksSnippetTests {
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 {
TemplateResourceResolver resolver = mock(TemplateResourceResolver.class);
given(resolver.resolveTemplateResource("links"))
.willReturn(
new FileSystemResource(
"src/test/resources/custom-snippet-templates/links-with-title.snippet"));
this.snippet.expectLinks("links-with-custom-attributes").withContents(
startsWith(".Title for the links"));
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")), attributes(key("title")
.value("Title for the links"))).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<>();
@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;
}
key("foo").value("bravo")))).document(operationBuilder(
"links-with-custom-descriptor-attributes").attribute(
TemplateEngine.class.getName(), new MustacheTemplateEngine(resolver))
.build());
}
}

View File

@@ -0,0 +1,47 @@
/*
* Copyright 2014-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* 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 java.io.IOException;
import org.springframework.restdocs.operation.OperationResponse;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
/**
* Stub implementation of {@code LinkExtractor} for testing.
*
* @author Andy Wilkinson
*/
class StubLinkExtractor implements LinkExtractor {
private MultiValueMap<String, Link> linksByRel = new LinkedMultiValueMap<>();
@Override
public MultiValueMap<String, Link> extractLinks(OperationResponse response)
throws IOException {
return this.linksByRel;
}
StubLinkExtractor withLinks(Link... links) {
for (Link link : links) {
this.linksByRel.add(link.getRel(), link);
}
return this;
}
}

View File

@@ -0,0 +1,74 @@
/*
* Copyright 2012-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* 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 java.io.IOException;
import java.util.Arrays;
import org.junit.Rule;
import org.junit.Test;
import org.springframework.core.io.FileSystemResource;
import org.springframework.restdocs.snippet.SnippetFormats;
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 static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath;
import static org.springframework.restdocs.snippet.SnippetFormats.asciidoctor;
import static org.springframework.restdocs.test.SnippetMatchers.tableWithHeader;
/**
* Tests for {@link RequestFieldsSnippet} that are specific to Asciidoctor.
*
* @author Andy Wilkinson
*/
public class AsciidoctorRequestFieldsSnippetTests {
@Rule
public ExpectedSnippet snippet = new ExpectedSnippet(SnippetFormats.asciidoctor());
@Test
public void requestFieldsWithListDescription() throws IOException {
TemplateResourceResolver resolver = mock(TemplateResourceResolver.class);
given(resolver.resolveTemplateResource("request-fields")).willReturn(
snippetResource("request-fields-with-list-description"));
this.snippet.expectRequestFields("request-fields-with-list-description")
.withContents(
tableWithHeader(asciidoctor(), "Path", "Type", "Description")
//
.row("a", "String", String.format(" - one%n - two"))
.configuration("[cols=\"1,1,1a\"]"));
new RequestFieldsSnippet(Arrays.asList(fieldWithPath("a").description(
Arrays.asList("one", "two"))))
.document(new OperationBuilder("request-fields-with-list-description",
this.snippet.getOutputDirectory())
.attribute(TemplateEngine.class.getName(),
new MustacheTemplateEngine(resolver))
.request("http://localhost").content("{\"a\": \"foo\"}").build());
}
private FileSystemResource snippetResource(String name) {
return new FileSystemResource("src/test/resources/custom-snippet-templates/adoc/"
+ name + ".snippet");
}
}

View File

@@ -0,0 +1,162 @@
/*
* Copyright 2012-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* 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 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.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.restdocs.snippet.SnippetException;
import org.springframework.restdocs.snippet.SnippetFormats;
import org.springframework.restdocs.test.ExpectedSnippet;
import org.springframework.restdocs.test.OperationBuilder;
import static org.hamcrest.CoreMatchers.endsWith;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.startsWith;
import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath;
/**
* Tests for failures when rendering {@link RequestFieldsSnippet} due to missing or
* undocumented fields.
*
* @author Andy Wilkinson
*/
public class RequestFieldsSnippetFailureTests {
@Rule
public ExpectedSnippet snippet = new ExpectedSnippet(SnippetFormats.asciidoctor());
@Rule
public ExpectedException thrown = ExpectedException.none();
@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 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());
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2015 the original author or authors.
* Copyright 2014-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,43 +18,33 @@ package org.springframework.restdocs.payload;
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.AbstractSnippetTests;
import org.springframework.restdocs.snippet.SnippetFormat;
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 static org.hamcrest.CoreMatchers.endsWith;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.startsWith;
import static org.hamcrest.CoreMatchers.containsString;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
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;
/**
* Tests for {@link RequestFieldsSnippet}.
*
* @author Andy Wilkinson
*/
public class RequestFieldsSnippetTests {
public class RequestFieldsSnippetTests extends AbstractSnippetTests {
@Rule
public final ExpectedException thrown = ExpectedException.none();
@Rule
public final ExpectedSnippet snippet = new ExpectedSnippet();
public RequestFieldsSnippetTests(String name, SnippetFormat snippetFormat) {
super(name, snippetFormat);
}
@Test
public void mapRequestWithFields() throws IOException {
@@ -65,9 +55,8 @@ public class RequestFieldsSnippetTests {
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")
fieldWithPath("a").description("three"))).document(operationBuilder(
"map-request-with-fields").request("http://localhost")
.content("{\"a\": {\"b\": 5, \"c\": \"charlie\"}}").build());
}
@@ -80,24 +69,11 @@ public class RequestFieldsSnippetTests {
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")
.description("three"))).document(operationBuilder(
"array-request-with-fields").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 ignoredRequestField() throws IOException {
this.snippet.expectRequestFields("ignored-request-field").withContents(
@@ -105,48 +81,25 @@ public class RequestFieldsSnippetTests {
"Field b"));
new RequestFieldsSnippet(Arrays.asList(fieldWithPath("a").ignored(),
fieldWithPath("b").description("Field b")))
.document(new OperationBuilder("ignored-request-field", this.snippet
.getOutputDirectory()).request("http://localhost")
.content("{\"a\": 5, \"b\": 4}").build());
fieldWithPath("b").description("Field b"))).document(operationBuilder(
"ignored-request-field").request("http://localhost")
.content("{\"a\": 5, \"b\": 4}").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());
}
public void requestFieldsWithCustomAttributes() throws IOException {
TemplateResourceResolver resolver = mock(TemplateResourceResolver.class);
given(resolver.resolveTemplateResource("request-fields")).willReturn(
snippetResource("request-fields-with-title"));
this.snippet.expectRequestFields("request-fields-with-custom-attributes")
.withContents(containsString("Custom title"));
@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());
new RequestFieldsSnippet(Arrays.asList(fieldWithPath("a").description("one")),
attributes(key("title").value("Custom title"))).document(operationBuilder(
"request-fields-with-custom-attributes")
.attribute(TemplateEngine.class.getName(),
new MustacheTemplateEngine(resolver)).request("http://localhost")
.content("{\"a\": \"foo\"}").build());
}
@Test
@@ -167,52 +120,13 @@ public class RequestFieldsSnippetTests {
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())
key("foo").value("charlie")))).document(operationBuilder(
"request-fields-with-custom-descriptor-attributes")
.attribute(TemplateEngine.class.getName(),
new MustacheTemplateEngine(resolver)).request("http://localhost")
.content("{\"a\": {\"b\": 5, \"c\": \"charlie\"}}").build());
}
@Test
public void requestFieldsWithCustomAttributes() throws IOException {
TemplateResourceResolver resolver = mock(TemplateResourceResolver.class);
given(resolver.resolveTemplateResource("request-fields")).willReturn(
snippetResource("request-fields-with-title"));
this.snippet.expectRequestFields("request-fields-with-custom-attributes")
.withContents(startsWith(".Custom title"));
new RequestFieldsSnippet(Arrays.asList(fieldWithPath("a").description("one")),
attributes(key("title").value("Custom title")))
.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 requestFieldsWithListDescription() throws IOException {
TemplateResourceResolver resolver = mock(TemplateResourceResolver.class);
given(resolver.resolveTemplateResource("request-fields")).willReturn(
snippetResource("request-fields-with-list-description"));
this.snippet.expectRequestFields("request-fields-with-list-description")
.withContents(
tableWithHeader("Path", "Type", "Description")
//
.row("a", "String", String.format(" - one%n - two"))
.configuration("[cols=\"1,1,1a\"]"));
new RequestFieldsSnippet(Arrays.asList(fieldWithPath("a").description(
Arrays.asList("one", "two"))))
.document(new OperationBuilder("request-fields-with-list-description",
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(
@@ -221,78 +135,11 @@ public class RequestFieldsSnippetTests {
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>")
fieldWithPath("a").description("three").type("a"))).document(operationBuilder(
"xml-request").request("http://localhost")
.content("<a><b>5</b><c>charlie</c></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,143 @@
/*
* Copyright 2012-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* 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 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.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.restdocs.snippet.SnippetException;
import org.springframework.restdocs.snippet.SnippetFormats;
import org.springframework.restdocs.test.ExpectedSnippet;
import org.springframework.restdocs.test.OperationBuilder;
import static org.hamcrest.CoreMatchers.endsWith;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.startsWith;
import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath;
/**
* Tests for failures when rendering {@link ResponseFieldsSnippet} due to missing or
* undocumented fields.
*
* @author Andy Wilkinson
*/
public class ResponseFieldsSnippetFailureTests {
@Rule
public ExpectedSnippet snippet = new ExpectedSnippet(SnippetFormats.asciidoctor());
@Rule
public ExpectedException thrown = ExpectedException.none();
@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 missingXmlAttribute() throws IOException {
this.thrown.expect(SnippetException.class);
this.thrown
.expectMessage(equalTo("Fields with the following paths were not found"
+ " in the payload: [a/@id]"));
new ResponseFieldsSnippet(Arrays.asList(fieldWithPath("a").description("one")
.type("b"), fieldWithPath("a/@id").description("two").type("c")))
.document(new OperationBuilder("missing-xml-attribute", this.snippet
.getOutputDirectory())
.response()
.content("<a>foo</a>")
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_XML_VALUE)
.build());
}
@Test
public void documentedXmlAttributesAreRemoved() throws IOException {
this.thrown.expect(SnippetException.class);
this.thrown.expectMessage(equalTo(String
.format("The following parts of the payload were not documented:"
+ "%n<a>bar</a>%n")));
new ResponseFieldsSnippet(Arrays.asList(fieldWithPath("a/@id").description("one")
.type("a"))).document(new OperationBuilder(
"documented-attribute-is-removed", this.snippet.getOutputDirectory())
.response().content("<a id=\"foo\">bar</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());
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2015 the original author or authors.
* Copyright 2014-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,43 +18,33 @@ package org.springframework.restdocs.payload;
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.AbstractSnippetTests;
import org.springframework.restdocs.snippet.SnippetFormat;
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 static org.hamcrest.CoreMatchers.endsWith;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.startsWith;
import static org.hamcrest.CoreMatchers.containsString;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
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;
/**
* Tests for {@link ResponseFieldsSnippet}.
*
* @author Andy Wilkinson
*/
public class ResponseFieldsSnippetTests {
public class ResponseFieldsSnippetTests extends AbstractSnippetTests {
@Rule
public final ExpectedException thrown = ExpectedException.none();
@Rule
public final ExpectedSnippet snippet = new ExpectedSnippet();
public ResponseFieldsSnippetTests(String name, SnippetFormat snippetFormat) {
super(name, snippetFormat);
}
@Test
public void mapResponseWithFields() throws IOException {
@@ -69,14 +59,12 @@ public class ResponseFieldsSnippetTests {
.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());
fieldWithPath("assets[].name").description("six"))).document(operationBuilder(
"map-response-with-fields")
.response()
.content(
"{\"id\": 67,\"date\": \"2015-01-20\",\"assets\":"
+ " [{\"id\":356,\"name\": \"sample\"}]}").build());
}
@Test
@@ -88,8 +76,7 @@ public class ResponseFieldsSnippetTests {
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()
.document(operationBuilder("array-response-with-fields").response()
.content("[{\"a\": {\"b\": 5}},{\"a\": {\"c\": \"charlie\"}}]")
.build());
}
@@ -101,8 +88,7 @@ public class ResponseFieldsSnippetTests {
tableWithHeader("Path", "Type", "Description").row("[]",
"String", "one"));
new ResponseFieldsSnippet(Arrays.asList(fieldWithPath("[]").description("one")))
.document(new OperationBuilder("array-response", this.snippet
.getOutputDirectory()).response()
.document(operationBuilder("array-response").response()
.content("[\"a\", \"b\", \"c\"]").build());
}
@@ -113,10 +99,25 @@ public class ResponseFieldsSnippetTests {
"Field b"));
new ResponseFieldsSnippet(Arrays.asList(fieldWithPath("a").ignored(),
fieldWithPath("b").description("Field b")))
.document(new OperationBuilder("ignored-response-field", this.snippet
.getOutputDirectory()).response().content("{\"a\": 5, \"b\": 4}")
.build());
fieldWithPath("b").description("Field b"))).document(operationBuilder(
"ignored-response-field").response().content("{\"a\": 5, \"b\": 4}")
.build());
}
@Test
public void responseFieldsWithCustomAttributes() throws IOException {
TemplateResourceResolver resolver = mock(TemplateResourceResolver.class);
given(resolver.resolveTemplateResource("response-fields")).willReturn(
snippetResource("response-fields-with-title"));
this.snippet.expectResponseFields("response-fields-with-custom-attributes")
.withContents(containsString("Custom title"));
new ResponseFieldsSnippet(Arrays.asList(fieldWithPath("a").description("one")),
attributes(key("title").value("Custom title"))).document(operationBuilder(
"response-fields-with-custom-attributes")
.attribute(TemplateEngine.class.getName(),
new MustacheTemplateEngine(resolver)).response()
.content("{\"a\": \"foo\"}").build());
}
@Test
@@ -137,31 +138,13 @@ public class ResponseFieldsSnippetTests {
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())
key("foo").value("charlie")))).document(operationBuilder(
"response-fields-with-custom-attributes")
.attribute(TemplateEngine.class.getName(),
new MustacheTemplateEngine(resolver)).response()
.content("{\"a\": {\"b\": 5, \"c\": \"charlie\"}}").build());
}
@Test
public void responseFieldsWithCustomAttributes() throws IOException {
TemplateResourceResolver resolver = mock(TemplateResourceResolver.class);
given(resolver.resolveTemplateResource("response-fields")).willReturn(
snippetResource("response-fields-with-title"));
this.snippet.expectResponseFields("response-fields-with-custom-attributes")
.withContents(startsWith(".Custom title"));
new ResponseFieldsSnippet(Arrays.asList(fieldWithPath("a").description("one")),
attributes(key("title").value("Custom title")))
.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(
@@ -169,28 +152,10 @@ public class ResponseFieldsSnippetTests {
.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());
fieldWithPath("a").description("three").type("a"))).document(operationBuilder(
"xml-response").response().content("<a><b>5</b><c>charlie</c></a>")
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_XML_VALUE)
.build());
}
@Test
@@ -200,30 +165,13 @@ public class ResponseFieldsSnippetTests {
"a/@id", "c", "two"));
new ResponseFieldsSnippet(Arrays.asList(fieldWithPath("a").description("one")
.type("b"), fieldWithPath("a/@id").description("two").type("c")))
.document(new OperationBuilder("xml-attribute", this.snippet
.getOutputDirectory())
.document(operationBuilder("xml-attribute")
.response()
.content("<a id=\"1\">foo</a>")
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_XML_VALUE)
.build());
}
@Test
public void missingXmlAttribute() throws IOException {
this.thrown.expect(SnippetException.class);
this.thrown
.expectMessage(equalTo("Fields with the following paths were not found"
+ " in the payload: [a/@id]"));
new ResponseFieldsSnippet(Arrays.asList(fieldWithPath("a").description("one")
.type("b"), fieldWithPath("a/@id").description("two").type("c")))
.document(new OperationBuilder("missing-xml-attribute", this.snippet
.getOutputDirectory())
.response()
.content("<a>foo</a>")
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_XML_VALUE)
.build());
}
@Test
public void missingOptionalXmlAttribute() throws IOException {
this.snippet.expectResponseFields("missing-optional-xml-attribute").withContents(
@@ -231,8 +179,7 @@ public class ResponseFieldsSnippetTests {
"a/@id", "c", "two"));
new ResponseFieldsSnippet(Arrays.asList(fieldWithPath("a").description("one")
.type("b"), fieldWithPath("a/@id").description("two").type("c")
.optional())).document(new OperationBuilder(
"missing-optional-xml-attribute", this.snippet.getOutputDirectory())
.optional())).document(operationBuilder("missing-optional-xml-attribute")
.response().content("<a>foo</a>")
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_XML_VALUE)
.build());
@@ -243,76 +190,10 @@ public class ResponseFieldsSnippetTests {
this.snippet.expectResponseFields("undocumented-attribute").withContents(
tableWithHeader("Path", "Type", "Description").row("a", "a", "one"));
new ResponseFieldsSnippet(Arrays.asList(fieldWithPath("a").description("one")
.type("a"))).document(new OperationBuilder("undocumented-attribute",
this.snippet.getOutputDirectory()).response()
.type("a"))).document(operationBuilder("undocumented-attribute").response()
.content("<a id=\"foo\">bar</a>")
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_XML_VALUE)
.build());
}
@Test
public void documentedXmlAttributesAreRemoved() throws IOException {
this.thrown.expect(SnippetException.class);
this.thrown.expectMessage(equalTo(String
.format("The following parts of the payload were not documented:"
+ "%n<a>bar</a>%n")));
new ResponseFieldsSnippet(Arrays.asList(fieldWithPath("a/@id").description("one")
.type("a"))).document(new OperationBuilder(
"documented-attribute-is-removed", this.snippet.getOutputDirectory())
.response().content("<a id=\"foo\">bar</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,85 @@
/*
* Copyright 2012-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* 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 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.restdocs.snippet.SnippetException;
import org.springframework.restdocs.snippet.SnippetFormats;
import org.springframework.restdocs.test.ExpectedSnippet;
import org.springframework.restdocs.test.OperationBuilder;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName;
/**
* Tests for failures when rendering {@link PathParametersSnippet} due to missing or
* undocumented path parameters.
*
* @author Andy Wilkinson
*/
public class PathParametersSnippetFailureTests {
@Rule
public ExpectedSnippet snippet = new ExpectedSnippet(SnippetFormats.asciidoctor());
@Rule
public ExpectedException thrown = ExpectedException.none();
@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());
}
}

View File

@@ -18,78 +18,30 @@ package org.springframework.restdocs.request;
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.AbstractSnippetTests;
import org.springframework.restdocs.snippet.SnippetFormat;
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 static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.startsWith;
import static org.hamcrest.CoreMatchers.containsString;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
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;
/**
* Tests for {@link PathParametersSnippet}.
*
* @author Andy Wilkinson
*
*/
public class PathParametersSnippetTests {
public class PathParametersSnippetTests extends AbstractSnippetTests {
@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());
public PathParametersSnippetTests(String name, SnippetFormat snippetFormat) {
super(name, snippetFormat);
}
@Test
@@ -99,9 +51,9 @@ public class PathParametersSnippetTests {
"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());
.description("two"))).document(operationBuilder("path-parameters")
.attribute("org.springframework.restdocs.urlTemplate", "/{a}/{b}")
.build());
}
@Test
@@ -110,10 +62,9 @@ public class PathParametersSnippetTests {
tableWithTitleAndHeader("/{a}/{b}", "Parameter", "Description").row("b",
"two"));
new PathParametersSnippet(Arrays.asList(parameterWithName("a").ignored(),
parameterWithName("b").description("two")))
.document(new OperationBuilder("ignored-path-parameter", this.snippet
.getOutputDirectory()).attribute(
"org.springframework.restdocs.urlTemplate", "/{a}/{b}").build());
parameterWithName("b").description("two"))).document(operationBuilder(
"ignored-path-parameter").attribute(
"org.springframework.restdocs.urlTemplate", "/{a}/{b}").build());
}
@Test
@@ -124,11 +75,28 @@ public class PathParametersSnippetTests {
.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());
.description("two"))).document(operationBuilder(
"path-parameters-with-query-string").attribute(
"org.springframework.restdocs.urlTemplate", "/{a}/{b}?foo=bar").build());
}
@Test
public void pathParametersWithCustomAttributes() throws IOException {
TemplateResourceResolver resolver = mock(TemplateResourceResolver.class);
given(resolver.resolveTemplateResource("path-parameters")).willReturn(
snippetResource("path-parameters-with-title"));
this.snippet.expectPathParameters("path-parameters-with-custom-attributes")
.withContents(containsString("The title"));
new PathParametersSnippet(Arrays.asList(parameterWithName("a").description("one")
.attributes(key("foo").value("alpha")), parameterWithName("b")
.description("two").attributes(key("foo").value("bravo"))),
attributes(key("title").value("The title"))).document(operationBuilder(
"path-parameters-with-custom-attributes")
.attribute("org.springframework.restdocs.urlTemplate", "/{a}/{b}")
.attribute(TemplateEngine.class.getName(),
new MustacheTemplateEngine(resolver)).build());
}
@Test
@@ -144,37 +112,10 @@ public class PathParametersSnippetTests {
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())
.document(operationBuilder("path-parameters-with-custom-descriptor-attributes")
.attribute("org.springframework.restdocs.urlTemplate", "/{a}/{b}")
.attribute(TemplateEngine.class.getName(),
new MustacheTemplateEngine(resolver)).build());
}
@Test
public void pathParametersWithCustomAttributes() throws IOException {
TemplateResourceResolver resolver = mock(TemplateResourceResolver.class);
given(resolver.resolveTemplateResource("path-parameters")).willReturn(
snippetResource("path-parameters-with-title"));
this.snippet.expectPathParameters("path-parameters-with-custom-attributes")
.withContents(startsWith(".The title"));
new PathParametersSnippet(Arrays.asList(parameterWithName("a").description("one")
.attributes(key("foo").value("alpha")), parameterWithName("b")
.description("two").attributes(key("foo").value("bravo"))),
attributes(key("title").value("The title")))
.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,84 @@
/*
* Copyright 2012-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* 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 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.restdocs.snippet.SnippetException;
import org.springframework.restdocs.snippet.SnippetFormats;
import org.springframework.restdocs.test.ExpectedSnippet;
import org.springframework.restdocs.test.OperationBuilder;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName;
/**
* Tests for failures when rendering {@link RequestParametersSnippet} due to missing or
* undocumented request parameters.
*
* @author Andy Wilkinson
*/
public class RequestParametersSnippetFailureTests {
@Rule
public ExpectedSnippet snippet = new ExpectedSnippet(SnippetFormats.asciidoctor());
@Rule
public ExpectedException thrown = ExpectedException.none();
@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());
}
}

View File

@@ -18,75 +18,30 @@ package org.springframework.restdocs.request;
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.AbstractSnippetTests;
import org.springframework.restdocs.snippet.SnippetFormat;
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 static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.startsWith;
import static org.hamcrest.CoreMatchers.containsString;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
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;
/**
* Tests for {@link RequestParametersSnippet}.
*
* @author Andy Wilkinson
*/
public class RequestParametersSnippetTests {
public class RequestParametersSnippetTests extends AbstractSnippetTests {
@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());
public RequestParametersSnippetTests(String name, SnippetFormat snippetFormat) {
super(name, snippetFormat);
}
@Test
@@ -96,8 +51,7 @@ public class RequestParametersSnippetTests {
"two"));
new RequestParametersSnippet(Arrays.asList(
parameterWithName("a").description("one"), parameterWithName("b")
.description("two"))).document(new OperationBuilder(
"request-parameters", this.snippet.getOutputDirectory())
.description("two"))).document(operationBuilder("request-parameters")
.request("http://localhost").param("a", "bravo").param("b", "bravo")
.build());
}
@@ -107,10 +61,29 @@ public class RequestParametersSnippetTests {
this.snippet.expectRequestParameters("ignored-request-parameter").withContents(
tableWithHeader("Parameter", "Description").row("b", "two"));
new RequestParametersSnippet(Arrays.asList(parameterWithName("a").ignored(),
parameterWithName("b").description("two")))
.document(new OperationBuilder("ignored-request-parameter", this.snippet
.getOutputDirectory()).request("http://localhost")
.param("a", "bravo").param("b", "bravo").build());
parameterWithName("b").description("two"))).document(operationBuilder(
"ignored-request-parameter").request("http://localhost")
.param("a", "bravo").param("b", "bravo").build());
}
@Test
public void requestParametersWithCustomAttributes() throws IOException {
TemplateResourceResolver resolver = mock(TemplateResourceResolver.class);
given(resolver.resolveTemplateResource("request-parameters")).willReturn(
snippetResource("request-parameters-with-title"));
this.snippet.expectRequestParameters("request-parameters-with-custom-attributes")
.withContents(containsString("The title"));
new RequestParametersSnippet(Arrays.asList(
parameterWithName("a").description("one").attributes(
key("foo").value("alpha")),
parameterWithName("b").description("two").attributes(
key("foo").value("bravo"))), attributes(key("title").value(
"The title"))).document(operationBuilder(
"request-parameters-with-custom-attributes")
.attribute(TemplateEngine.class.getName(),
new MustacheTemplateEngine(resolver)).request("http://localhost")
.param("a", "alpha").param("b", "bravo").build());
}
@Test
@@ -127,38 +100,11 @@ public class RequestParametersSnippetTests {
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())
key("foo").value("bravo")))).document(operationBuilder(
"request-parameters-with-custom-descriptor-attributes")
.attribute(TemplateEngine.class.getName(),
new MustacheTemplateEngine(resolver)).request("http://localhost")
.param("a", "alpha").param("b", "bravo").build());
}
@Test
public void requestParametersWithCustomAttributes() throws IOException {
TemplateResourceResolver resolver = mock(TemplateResourceResolver.class);
given(resolver.resolveTemplateResource("request-parameters")).willReturn(
snippetResource("request-parameters-with-title"));
this.snippet.expectRequestParameters("request-parameters-with-custom-attributes")
.withContents(startsWith(".The title"));
new RequestParametersSnippet(Arrays.asList(
parameterWithName("a").description("one").attributes(
key("foo").value("alpha")),
parameterWithName("b").description("two").attributes(
key("foo").value("bravo"))), attributes(key("title").value(
"The title"))).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

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2015 the original author or authors.
* Copyright 2014-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -26,6 +26,7 @@ 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 static org.springframework.restdocs.snippet.SnippetFormats.asciidoctor;
/**
* Tests for {@link StandardWriterResolver}.
@@ -37,7 +38,7 @@ public class StandardWriterResolverTests {
private final PlaceholderResolver placeholderResolver = mock(PlaceholderResolver.class);
private final StandardWriterResolver resolver = new StandardWriterResolver(
this.placeholderResolver);
this.placeholderResolver, "UTF-8", asciidoctor());
@Test
public void noConfiguredOutputDirectoryAndRelativeInput() {

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2015 the original author or authors.
* Copyright 2014-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -29,6 +29,7 @@ import org.springframework.core.io.Resource;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;
import static org.springframework.restdocs.snippet.SnippetFormats.asciidoctor;
/**
* Tests for {@link TemplateResourceResolver}.
@@ -40,7 +41,8 @@ public class StandardTemplateResourceResolverTests {
@Rule
public final ExpectedException thrown = ExpectedException.none();
private final TemplateResourceResolver resolver = new StandardTemplateResourceResolver();
private final TemplateResourceResolver resolver = new StandardTemplateResourceResolver(
asciidoctor());
private final TestClassLoader classLoader = new TestClassLoader();
@@ -66,7 +68,7 @@ public class StandardTemplateResourceResolverTests {
@Test
public void fallsBackToDefaultSnippet() throws Exception {
this.classLoader.addResource(
"org/springframework/restdocs/templates/default-test.snippet", getClass()
"org/springframework/restdocs/templates/adoc/test.snippet", getClass()
.getResource("test.snippet"));
Resource snippet = doWithThreadContextClassLoader(this.classLoader,
new Callable<Resource>() {

View File

@@ -23,6 +23,7 @@ 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.SnippetFormat;
import org.springframework.restdocs.snippet.TemplatedSnippet;
import org.springframework.restdocs.test.SnippetMatchers.SnippetMatcher;
@@ -38,14 +39,21 @@ import static org.junit.Assert.assertThat;
*/
public class ExpectedSnippet implements TestRule {
private final SnippetFormat snippetFormat;
private final SnippetMatcher snippet;
private String expectedName;
private String expectedType;
private SnippetMatcher snippet = SnippetMatchers.snippet();
private File outputDirectory;
public ExpectedSnippet(SnippetFormat snippetFormat) {
this.snippetFormat = snippetFormat;
this.snippet = SnippetMatchers.snippet(snippetFormat);
}
@Override
public Statement apply(final Statement base, Description description) {
this.outputDirectory = new File("build/"
@@ -56,7 +64,8 @@ public class ExpectedSnippet implements TestRule {
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");
File snippetFile = new File(snippetDir, this.expectedType + "."
+ this.snippetFormat.getFileExtension());
assertThat(snippetFile, is(this.snippet));
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2015 the original author or authors.
* Copyright 2014-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -37,6 +37,8 @@ import org.springframework.restdocs.operation.OperationResponseFactory;
import org.springframework.restdocs.operation.Parameters;
import org.springframework.restdocs.operation.StandardOperation;
import org.springframework.restdocs.snippet.RestDocumentationContextPlaceholderResolver;
import org.springframework.restdocs.snippet.SnippetFormat;
import org.springframework.restdocs.snippet.SnippetFormats;
import org.springframework.restdocs.snippet.StandardWriterResolver;
import org.springframework.restdocs.snippet.WriterResolver;
import org.springframework.restdocs.templates.StandardTemplateResourceResolver;
@@ -58,11 +60,18 @@ public class OperationBuilder {
private final File outputDirectory;
private final SnippetFormat snippetFormat;
private OperationRequestBuilder requestBuilder;
public OperationBuilder(String name, File outputDirectory) {
this(name, outputDirectory, SnippetFormats.asciidoctor());
}
public OperationBuilder(String name, File outputDirectory, SnippetFormat snippetFormat) {
this.name = name;
this.outputDirectory = outputDirectory;
this.snippetFormat = snippetFormat;
}
public OperationRequestBuilder request(String uri) {
@@ -82,13 +91,15 @@ public class OperationBuilder {
public Operation build() {
if (this.attributes.get(TemplateEngine.class.getName()) == null) {
this.attributes.put(TemplateEngine.class.getName(),
new MustacheTemplateEngine(new StandardTemplateResourceResolver()));
new MustacheTemplateEngine(new StandardTemplateResourceResolver(
this.snippetFormat)));
}
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)));
new RestDocumentationContextPlaceholderResolver(context), "UTF-8",
this.snippetFormat));
return new StandardOperation(this.name,
(this.requestBuilder == null ? new OperationRequestBuilder(
"http://localhost/").buildRequest() : this.requestBuilder

View File

@@ -30,6 +30,8 @@ import org.hamcrest.BaseMatcher;
import org.hamcrest.Description;
import org.hamcrest.Matcher;
import org.springframework.http.HttpStatus;
import org.springframework.restdocs.snippet.SnippetFormat;
import org.springframework.restdocs.snippet.SnippetFormats;
import org.springframework.util.FileCopyUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestMethod;
@@ -45,37 +47,62 @@ public final class SnippetMatchers {
}
public static SnippetMatcher snippet() {
return new SnippetMatcher();
public static SnippetMatcher snippet(SnippetFormat snippetFormat) {
return new SnippetMatcher(snippetFormat);
}
public static AsciidoctorTableMatcher tableWithTitleAndHeader(String title,
String... headers) {
return new AsciidoctorTableMatcher(title, headers);
public static TableMatcher<?> tableWithHeader(SnippetFormat format, String... headers) {
if ("adoc".equals(format.getFileExtension())) {
return new AsciidoctorTableMatcher(null, headers);
}
return new MarkdownTableMatcher(null, headers);
}
public static AsciidoctorTableMatcher tableWithHeader(String... headers) {
return new AsciidoctorTableMatcher(null, headers);
public static TableMatcher<?> tableWithTitleAndHeader(SnippetFormat format,
String title, String... headers) {
if ("adoc".equals(format.getFileExtension())) {
return new AsciidoctorTableMatcher(title, headers);
}
return new MarkdownTableMatcher(title, headers);
}
public static HttpRequestMatcher httpRequest(RequestMethod method, String uri) {
return new HttpRequestMatcher(method, uri);
public static HttpRequestMatcher httpRequest(SnippetFormat format,
RequestMethod requestMethod, String uri) {
if ("adoc".equals(format.getFileExtension())) {
return new HttpRequestMatcher(requestMethod, uri,
new AsciidoctorCodeBlockMatcher<>("http"), 3);
}
return new HttpRequestMatcher(requestMethod, uri, new MarkdownCodeBlockMatcher<>(
"http"), 2);
}
public static HttpResponseMatcher httpResponse(HttpStatus status) {
return new HttpResponseMatcher(status);
public static HttpResponseMatcher httpResponse(SnippetFormat format, HttpStatus status) {
if ("adoc".equals(format.getFileExtension())) {
return new HttpResponseMatcher(status, new AsciidoctorCodeBlockMatcher<>(
"http"), 3);
}
return new HttpResponseMatcher(status, new MarkdownCodeBlockMatcher<>("http"), 2);
}
@SuppressWarnings({ "rawtypes" })
public static AsciidoctorCodeBlockMatcher<?> codeBlock(String language) {
return new AsciidoctorCodeBlockMatcher(language);
public static CodeBlockMatcher<?> codeBlock(SnippetFormat format, String language) {
if ("adoc".equals(format.getFileExtension())) {
return new AsciidoctorCodeBlockMatcher(language);
}
return new MarkdownCodeBlockMatcher(language);
}
private static abstract class AbstractSnippetContentMatcher extends
BaseMatcher<String> {
private final SnippetFormat snippetFormat;
private List<String> lines = new ArrayList<>();
protected AbstractSnippetContentMatcher(SnippetFormat snippetFormat) {
this.snippetFormat = snippetFormat;
}
protected void addLine(String line) {
this.lines.add(line);
}
@@ -94,7 +121,7 @@ public final class SnippetMatchers {
@Override
public void describeTo(Description description) {
description.appendText("Asciidoctor snippet");
description.appendText(this.snippetFormat.getFileExtension() + " snippet");
description.appendText(getLinesAsString());
}
@@ -123,17 +150,15 @@ public final class SnippetMatchers {
}
/**
* A {@link Matcher} for an Asciidoctor code block.
* Base class for code block matchers.
*
* @param <T> The type of the matcher
*/
public static class AsciidoctorCodeBlockMatcher<T extends AsciidoctorCodeBlockMatcher<T>>
extends AbstractSnippetContentMatcher {
public static class CodeBlockMatcher<T extends CodeBlockMatcher<T>> extends
AbstractSnippetContentMatcher {
protected AsciidoctorCodeBlockMatcher(String language) {
this.addLine("[source," + language + "]");
this.addLine("----");
this.addLine("----");
protected CodeBlockMatcher(SnippetFormat snippetFormat) {
super(snippetFormat);
}
@SuppressWarnings("unchecked")
@@ -144,32 +169,84 @@ public final class SnippetMatchers {
}
/**
* A {@link Matcher} for an Asciidoctor code block.
*
* @param <T> The type of the matcher
*/
public static class AsciidoctorCodeBlockMatcher<T extends AsciidoctorCodeBlockMatcher<T>>
extends CodeBlockMatcher<T> {
protected AsciidoctorCodeBlockMatcher(String language) {
super(SnippetFormats.asciidoctor());
this.addLine("[source," + language + "]");
this.addLine("----");
this.addLine("----");
}
}
/**
* A {@link Matcher} for a Markdown code block.
*
* @param <T> The type of the matcher
*/
public static class MarkdownCodeBlockMatcher<T extends MarkdownCodeBlockMatcher<T>>
extends CodeBlockMatcher<T> {
protected MarkdownCodeBlockMatcher(String language) {
super(SnippetFormats.markdown());
this.addLine("```" + language);
this.addLine("```");
}
}
/**
* A {@link Matcher} for an HTTP request or response.
*
* @param <T> The type of the matcher
*/
public static abstract class HttpMatcher<T extends HttpMatcher<T>> extends
AsciidoctorCodeBlockMatcher<HttpMatcher<T>> {
BaseMatcher<String> {
private int headerOffset = 3;
private final CodeBlockMatcher<?> delegate;
protected HttpMatcher() {
super("http");
private int headerOffset;
protected HttpMatcher(CodeBlockMatcher<?> delegate, int headerOffset) {
this.delegate = delegate;
this.headerOffset = headerOffset;
}
@SuppressWarnings("unchecked")
public T header(String name, String value) {
this.addLine(this.headerOffset++, name + ": " + value);
this.delegate.addLine(this.headerOffset++, name + ": " + value);
return (T) this;
}
@SuppressWarnings("unchecked")
public T header(String name, long value) {
this.addLine(this.headerOffset++, name + ": " + value);
this.delegate.addLine(this.headerOffset++, name + ": " + value);
return (T) this;
}
@SuppressWarnings("unchecked")
public T content(String content) {
this.delegate.addLine(-1, content);
return (T) this;
}
@Override
public boolean matches(Object item) {
return this.delegate.matches(item);
}
@Override
public void describeTo(Description description) {
this.delegate.describeTo(description);
}
}
/**
@@ -178,7 +255,9 @@ public final class SnippetMatchers {
public static final class HttpResponseMatcher extends
HttpMatcher<HttpResponseMatcher> {
private HttpResponseMatcher(HttpStatus status) {
private HttpResponseMatcher(HttpStatus status, CodeBlockMatcher<?> delegate,
int headerOffset) {
super(delegate, headerOffset);
this.content("HTTP/1.1 " + status.value() + " " + status.getReasonPhrase());
this.content("");
}
@@ -190,20 +269,41 @@ public final class SnippetMatchers {
*/
public static final class HttpRequestMatcher extends HttpMatcher<HttpRequestMatcher> {
private HttpRequestMatcher(RequestMethod requestMethod, String uri) {
private HttpRequestMatcher(RequestMethod requestMethod, String uri,
CodeBlockMatcher<?> delegate, int headerOffset) {
super(delegate, headerOffset);
this.content(requestMethod.name() + " " + uri + " HTTP/1.1");
this.content("");
}
}
/**
* Base class for table matchers.
*
* @param <T> The concrete type of the matcher
*/
public static abstract class TableMatcher<T extends TableMatcher<T>> extends
AbstractSnippetContentMatcher {
protected TableMatcher(SnippetFormat snippetFormat) {
super(snippetFormat);
}
public abstract T row(String... entries);
public abstract T configuration(String configuration);
}
/**
* A {@link Matcher} for an Asciidoctor table.
*/
public static final class AsciidoctorTableMatcher extends
AbstractSnippetContentMatcher {
TableMatcher<AsciidoctorTableMatcher> {
private AsciidoctorTableMatcher(String title, String... columns) {
super(SnippetFormats.asciidoctor());
if (StringUtils.hasText(title)) {
this.addLine("." + title);
}
@@ -216,6 +316,7 @@ public final class SnippetMatchers {
this.addLine("|===");
}
@Override
public AsciidoctorTableMatcher row(String... entries) {
for (String entry : entries) {
this.addLine(-1, "|" + entry);
@@ -224,19 +325,68 @@ public final class SnippetMatchers {
return this;
}
@Override
public AsciidoctorTableMatcher configuration(String configuration) {
this.addLine(0, configuration);
return this;
}
}
/**
* A {@link Matcher} for a Markdown table.
*/
public static final class MarkdownTableMatcher extends
TableMatcher<MarkdownTableMatcher> {
private MarkdownTableMatcher(String title, String... columns) {
super(SnippetFormats.asciidoctor());
if (StringUtils.hasText(title)) {
this.addLine(title);
}
String header = StringUtils.collectionToDelimitedString(
Arrays.asList(columns), " | ");
this.addLine(header);
List<String> components = new ArrayList<>();
for (String column : columns) {
StringBuilder dashes = new StringBuilder();
for (int i = 0; i < column.length(); i++) {
dashes.append("-");
}
components.add(dashes.toString());
}
this.addLine(StringUtils.collectionToDelimitedString(components, " | "));
this.addLine("");
}
@Override
public MarkdownTableMatcher row(String... entries) {
this.addLine(-1, StringUtils.collectionToDelimitedString(
Arrays.asList(entries), " | "));
return this;
}
@Override
public MarkdownTableMatcher configuration(String configuration) {
throw new UnsupportedOperationException(
"Markdown does not support table configuration");
}
}
/**
* A {@link Matcher} for a snippet file.
*/
public static class SnippetMatcher extends BaseMatcher<File> {
public static final class SnippetMatcher extends BaseMatcher<File> {
private final SnippetFormat snippetFormat;
private Matcher<String> expectedContents;
private SnippetMatcher(SnippetFormat snippetFormat) {
this.snippetFormat = snippetFormat;
}
@Override
public boolean matches(Object item) {
if (snippetFileExists(item)) {
@@ -285,7 +435,8 @@ public final class SnippetMatchers {
this.expectedContents.describeTo(description);
}
else {
description.appendText("Asciidoctor snippet");
description
.appendText(this.snippetFormat.getFileExtension() + " snippet");
}
}
@@ -295,4 +446,5 @@ public final class SnippetMatchers {
}
}
}

View File

@@ -0,0 +1,4 @@
{{title}}
```bash
$ curl {{url}} {{options}}
```

View File

@@ -0,0 +1,8 @@
{{title}}
```http
{{method}} {{path}} HTTP/1.1
{{#headers}}
{{name}}: {{value}}
{{/headers}}
{{requestBody}}
```

View File

@@ -0,0 +1,8 @@
{{title}}
```http
HTTP/1.1 {{statusCode}} {{statusReason}}
{{#headers}}
{{name}}: {{value}}
{{/headers}}
{{responseBody}}
```

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,5 @@
Name | Description | Foo
---- | ----------- | ---
{{#headers}}
{{name}} | {{description}} | {{foo}}
{{/headers}}

View File

@@ -0,0 +1,6 @@
{{title}}
Name | Description
---- | -----------
{{#headers}}
{{name}} | {{description}}
{{/headers}}

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,5 @@
Name | Description | Foo
---- | ----------- | ---
{{#headers}}
{{name}} | {{description}} | {{foo}}
{{/headers}}

View File

@@ -0,0 +1,6 @@
{{title}}
Name | Description
---- | -----------
{{#headers}}
{{name}} | {{description}}
{{/headers}}