Merge branch '2.0.x'

This commit is contained in:
Andy Wilkinson
2022-01-13 12:37:09 +00:00
19 changed files with 675 additions and 453 deletions

View File

@@ -0,0 +1,7 @@
<?xml version="1.0"?>
<!DOCTYPE suppressions PUBLIC
"-//Checkstyle//DTD SuppressionFilter Configuration 1.2//EN"
"https://checkstyle.org/dtds/suppressions_1_2.dtd">
<suppressions>
<suppress files="com[\\/]example[\\/]restassured" checks="IllegalImport" />
</suppressions>

View File

@@ -1,8 +1,16 @@
<?xml version="1.0"?>
<!DOCTYPE module PUBLIC "-//Puppy Crawl//DTD Check Configuration 1.3//EN" "https://checkstyle.org/dtds/configuration_1_3.dtd">
<module name="com.puppycrawl.tools.checkstyle.Checker">
<module name="SuppressionFilter">
<property name="file" value="${config_loc}/checkstyle-suppressions.xml"/>
</module>
<module name="io.spring.javaformat.checkstyle.SpringChecks">
<property name="avoidStaticImportExcludes" value=" org.springframework.restdocs.cli.CliDocumentation.*"/>
</module>
<module name="com.puppycrawl.tools.checkstyle.TreeWalker">
<module name="com.puppycrawl.tools.checkstyle.checks.imports.IllegalImportCheck">
<property name="regexp" value="true" />
<property name="illegalPkgs" value="^org\.hamcrest.*" />
</module>
</module>
</module>

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2021 the original author or authors.
* Copyright 2014-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -23,11 +23,10 @@ import java.util.Map;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.junit.rules.TemporaryFolder;
import static org.assertj.core.api.Assertions.assertThat;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
/**
* Tests for {@link SnippetsDirectoryResolver}.
@@ -39,9 +38,6 @@ public class SnippetsDirectoryResolverTests {
@Rule
public TemporaryFolder temporaryFolder = new TemporaryFolder();
@Rule
public ExpectedException thrown = ExpectedException.none();
@Test
public void mavenProjectsUseTargetGeneratedSnippetsRelativeToDocdir() throws IOException {
this.temporaryFolder.newFile("pom.xml");
@@ -57,17 +53,15 @@ public class SnippetsDirectoryResolverTests {
Map<String, Object> attributes = new HashMap<>();
String docdir = new File(this.temporaryFolder.getRoot(), "src/main/asciidoc").getAbsolutePath();
attributes.put("docdir", docdir);
this.thrown.expect(IllegalStateException.class);
this.thrown.expectMessage(equalTo("pom.xml not found in '" + docdir + "' or above"));
getMavenSnippetsDirectory(attributes);
assertThatIllegalStateException().isThrownBy(() -> getMavenSnippetsDirectory(attributes))
.withMessage("pom.xml not found in '" + docdir + "' or above");
}
@Test
public void illegalStateWhenDocdirAttributeIsNotSetInMavenProject() throws IOException {
Map<String, Object> attributes = new HashMap<>();
this.thrown.expect(IllegalStateException.class);
this.thrown.expectMessage(equalTo("docdir attribute not found"));
getMavenSnippetsDirectory(attributes);
assertThatIllegalStateException().isThrownBy(() -> getMavenSnippetsDirectory(attributes))
.withMessage("docdir attribute not found");
}
@Test
@@ -100,9 +94,9 @@ public class SnippetsDirectoryResolverTests {
@Test
public void illegalStateWhenGradleProjectdirAndProjectdirAttributesAreNotSetInGradleProject() throws IOException {
Map<String, Object> attributes = new HashMap<>();
this.thrown.expect(IllegalStateException.class);
this.thrown.expectMessage(equalTo("projectdir attribute not found"));
new SnippetsDirectoryResolver().getSnippetsDirectory(attributes);
assertThatIllegalStateException()
.isThrownBy(() -> new SnippetsDirectoryResolver().getSnippetsDirectory(attributes))
.withMessage("projectdir attribute not found");
}
private File getMavenSnippetsDirectory(Map<String, Object> attributes) {

View File

@@ -63,8 +63,6 @@ dependencies {
testImplementation("org.assertj:assertj-core")
testImplementation("org.javamoney:moneta")
testImplementation("org.mockito:mockito-core")
testImplementation("org.hamcrest:hamcrest-core")
testImplementation("org.hamcrest:hamcrest-library")
testImplementation("org.springframework:spring-test")
testRuntimeOnly("org.glassfish:jakarta.el:4.0.2")

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2021 the original author or authors.
* Copyright 2014-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -21,14 +21,12 @@ 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.templates.TemplateFormats;
import org.springframework.restdocs.testfixtures.OperationBuilder;
import static org.hamcrest.CoreMatchers.endsWith;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.springframework.restdocs.headers.HeaderDocumentation.headerWithName;
/**
@@ -42,25 +40,21 @@ public class RequestHeadersSnippetFailureTests {
@Rule
public OperationBuilder operationBuilder = new OperationBuilder(TemplateFormats.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(this.operationBuilder.request("http://localhost").build());
assertThatExceptionOfType(SnippetException.class)
.isThrownBy(() -> new RequestHeadersSnippet(Arrays.asList(headerWithName("Accept").description("one")))
.document(this.operationBuilder.request("http://localhost").build()))
.withMessage("Headers with the following names were not found in the request: [Accept]");
}
@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(this.operationBuilder.request("http://localhost").header("X-Test", "test").build());
assertThatExceptionOfType(SnippetException.class)
.isThrownBy(() -> new RequestHeadersSnippet(Arrays.asList(headerWithName("Accept").description("one")))
.document(this.operationBuilder.request("http://localhost").header("X-Test", "test").build()))
.withMessageEndingWith("Headers with the following names were not found in the request: [Accept]");
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2021 the original author or authors.
* Copyright 2014-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -21,14 +21,12 @@ 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.templates.TemplateFormats;
import org.springframework.restdocs.testfixtures.OperationBuilder;
import static org.hamcrest.CoreMatchers.endsWith;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.springframework.restdocs.headers.HeaderDocumentation.headerWithName;
/**
@@ -42,25 +40,21 @@ public class ResponseHeadersSnippetFailureTests {
@Rule
public OperationBuilder operationBuilder = new OperationBuilder(TemplateFormats.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(this.operationBuilder.response().build());
assertThatExceptionOfType(SnippetException.class).isThrownBy(
() -> new ResponseHeadersSnippet(Arrays.asList(headerWithName("Content-Type").description("one")))
.document(this.operationBuilder.response().build()))
.withMessage("Headers with the following names were not found" + " in the response: [Content-Type]");
}
@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(this.operationBuilder.response().header("X-Test", "test").build());
assertThatExceptionOfType(SnippetException.class).isThrownBy(
() -> new ResponseHeadersSnippet(Arrays.asList(headerWithName("Content-Type").description("one")))
.document(this.operationBuilder.response().header("X-Test", "test").build()))
.withMessageEndingWith(
"Headers with the following names were not found in the response: [Content-Type]");
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2021 the original author or authors.
* Copyright 2014-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -22,13 +22,12 @@ 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.templates.TemplateFormats;
import org.springframework.restdocs.testfixtures.OperationBuilder;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
/**
* Tests for failures when rendering {@link LinksSnippet} due to missing or undocumented
@@ -41,42 +40,40 @@ public class LinksSnippetFailureTests {
@Rule
public OperationBuilder operationBuilder = new OperationBuilder(TemplateFormats.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(this.operationBuilder.build());
assertThatExceptionOfType(SnippetException.class)
.isThrownBy(() -> new LinksSnippet(new StubLinkExtractor().withLinks(new Link("foo", "bar")),
Collections.<LinkDescriptor>emptyList()).document(this.operationBuilder.build()))
.withMessage("Links with the following relations were not documented: [foo]");
}
@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(this.operationBuilder.build());
assertThatExceptionOfType(SnippetException.class)
.isThrownBy(() -> new LinksSnippet(new StubLinkExtractor(),
Arrays.asList(new LinkDescriptor("foo").description("bar")))
.document(this.operationBuilder.build()))
.withMessage("Links with the following relations were not found in the response: [foo]");
}
@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(this.operationBuilder.build());
assertThatExceptionOfType(SnippetException.class)
.isThrownBy(() -> new LinksSnippet(new StubLinkExtractor().withLinks(new Link("a", "alpha")),
Arrays.asList(new LinkDescriptor("foo").description("bar")))
.document(this.operationBuilder.build()))
.withMessage("Links with the following relations were not documented: [a]. Links with the following"
+ " relations were not found in the response: [foo]");
}
@Test
public void linkWithNoDescription() throws IOException {
this.thrown.expect(SnippetException.class);
this.thrown.expectMessage(equalTo("No description was provided for the link with rel 'foo' and no"
+ " title was available from the link in the payload"));
new LinksSnippet(new StubLinkExtractor().withLinks(new Link("foo", "bar")),
Arrays.asList(new LinkDescriptor("foo"))).document(this.operationBuilder.build());
assertThatExceptionOfType(SnippetException.class)
.isThrownBy(() -> new LinksSnippet(new StubLinkExtractor().withLinks(new Link("foo", "bar")),
Arrays.asList(new LinkDescriptor("foo"))).document(this.operationBuilder.build()))
.withMessage("No description was provided for the link with rel 'foo' and no title was available"
+ " from the link in the payload");
}
}

View File

@@ -19,12 +19,10 @@ package org.springframework.restdocs.operation;
import java.net.URI;
import java.util.Arrays;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import static org.assertj.core.api.Assertions.assertThat;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
/**
* Tests for {@link QueryStringParser}.
@@ -33,9 +31,6 @@ import static org.hamcrest.CoreMatchers.equalTo;
*/
public class QueryStringParserTests {
@Rule
public ExpectedException thrown = ExpectedException.none();
private final QueryStringParser queryStringParser = new QueryStringParser();
@Test
@@ -76,9 +71,9 @@ public class QueryStringParserTests {
@Test
public void malformedParameter() {
this.thrown.expect(IllegalArgumentException.class);
this.thrown.expectMessage(equalTo("The parameter 'a=apple=avocado' is malformed"));
this.queryStringParser.parse(URI.create("http://localhost?a=apple=avocado"));
assertThatIllegalArgumentException()
.isThrownBy(() -> this.queryStringParser.parse(URI.create("http://localhost?a=apple=avocado")))
.withMessage("The parameter 'a=apple=avocado' is malformed");
}
@Test

View File

@@ -23,10 +23,9 @@ import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.Rule;
import org.junit.Test;
import org.springframework.restdocs.testfixtures.OutputCapture;
import org.springframework.restdocs.testfixtures.OutputCaptureRule;
import static org.assertj.core.api.Assertions.assertThat;
import static org.hamcrest.Matchers.isEmptyString;
/**
* Tests for {@link PrettyPrintingContentModifier}.
@@ -37,7 +36,7 @@ import static org.hamcrest.Matchers.isEmptyString;
public class PrettyPrintingContentModifierTests {
@Rule
public OutputCapture outputCapture = new OutputCapture();
public OutputCaptureRule outputCapture = new OutputCaptureRule();
@Test
public void prettyPrintJson() throws Exception {
@@ -61,17 +60,17 @@ public class PrettyPrintingContentModifierTests {
@Test
public void nonJsonAndNonXmlContentIsHandledGracefully() throws Exception {
String content = "abcdefg";
this.outputCapture.expect(isEmptyString());
assertThat(new PrettyPrintingContentModifier().modifyContent(content.getBytes(), null))
.isEqualTo(content.getBytes());
assertThat(this.outputCapture).isEmpty();
}
@Test
public void nonJsonContentThatInitiallyLooksLikeJsonIsHandledGracefully() throws Exception {
String content = "\"abc\",\"def\"";
this.outputCapture.expect(isEmptyString());
assertThat(new PrettyPrintingContentModifier().modifyContent(content.getBytes(), null))
.isEqualTo(content.getBytes());
assertThat(this.outputCapture).isEmpty();
}
@Test

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2021 the original author or authors.
* Copyright 2014-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -22,7 +22,6 @@ 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;
@@ -30,9 +29,7 @@ import org.springframework.restdocs.snippet.SnippetException;
import org.springframework.restdocs.templates.TemplateFormats;
import org.springframework.restdocs.testfixtures.OperationBuilder;
import static org.hamcrest.CoreMatchers.endsWith;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.startsWith;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath;
/**
@@ -46,148 +43,145 @@ public class RequestFieldsSnippetFailureTests {
@Rule
public OperationBuilder operationBuilder = new OperationBuilder(TemplateFormats.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(this.operationBuilder.request("http://localhost").content("{\"a\": 5}").build());
assertThatExceptionOfType(SnippetException.class)
.isThrownBy(() -> new RequestFieldsSnippet(Collections.<FieldDescriptor>emptyList())
.document(this.operationBuilder.request("http://localhost").content("{\"a\": 5}").build()))
.withMessageStartingWith("The following parts of the payload were not documented:");
}
@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(this.operationBuilder.request("http://localhost").content("{}").build());
assertThatExceptionOfType(SnippetException.class)
.isThrownBy(() -> new RequestFieldsSnippet(Arrays.asList(fieldWithPath("a.b").description("one")))
.document(this.operationBuilder.request("http://localhost").content("{}").build()))
.withMessage("Fields with the following paths were not found in the payload: [a.b]");
}
@Test
public void missingOptionalRequestFieldWithNoTypeProvided() throws IOException {
this.thrown.expect(FieldTypeRequiredException.class);
new RequestFieldsSnippet(Arrays.asList(fieldWithPath("a.b").description("one").optional()))
.document(this.operationBuilder.request("http://localhost").content("{ }").build());
assertThatExceptionOfType(FieldTypeRequiredException.class).isThrownBy(
() -> new RequestFieldsSnippet(Arrays.asList(fieldWithPath("a.b").description("one").optional()))
.document(this.operationBuilder.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(this.operationBuilder.request("http://localhost").content("{ \"a\": { \"c\": 5 }}").build());
assertThatExceptionOfType(SnippetException.class).isThrownBy(
() -> new RequestFieldsSnippet(Arrays.asList(fieldWithPath("a.b").description("one"))).document(
this.operationBuilder.request("http://localhost").content("{ \"a\": { \"c\": 5 }}").build()))
.withMessageStartingWith("The following parts of the payload were not documented:")
.withMessageEndingWith("Fields with the following paths were not found in the payload: [a.b]");
}
@Test
public void attemptToDocumentFieldsWithNoRequestBody() throws IOException {
this.thrown.expect(SnippetException.class);
this.thrown.expectMessage(equalTo("Cannot document request fields as the request body is empty"));
new RequestFieldsSnippet(Arrays.asList(fieldWithPath("a").description("one")))
.document(this.operationBuilder.request("http://localhost").build());
assertThatExceptionOfType(SnippetException.class)
.isThrownBy(() -> new RequestFieldsSnippet(Arrays.asList(fieldWithPath("a").description("one")))
.document(this.operationBuilder.request("http://localhost").build()))
.withMessage("Cannot document request fields as the request body is empty");
}
@Test
public void fieldWithExplicitTypeThatDoesNotMatchThePayload() throws IOException {
this.thrown.expect(FieldTypesDoNotMatchException.class);
this.thrown.expectMessage(
equalTo("The documented type of the field 'a' is" + " Object but the actual type is Number"));
new RequestFieldsSnippet(Arrays.asList(fieldWithPath("a").description("one").type(JsonFieldType.OBJECT)))
.document(this.operationBuilder.request("http://localhost").content("{ \"a\": 5 }").build());
assertThatExceptionOfType(FieldTypesDoNotMatchException.class)
.isThrownBy(() -> new RequestFieldsSnippet(
Arrays.asList(fieldWithPath("a").description("one").type(JsonFieldType.OBJECT))).document(
this.operationBuilder.request("http://localhost").content("{ \"a\": 5 }").build()))
.withMessage("The documented type of the field 'a' is Object but the actual type is Number");
}
@Test
public void fieldWithExplicitSpecificTypeThatActuallyVaries() throws IOException {
this.thrown.expect(FieldTypesDoNotMatchException.class);
this.thrown.expectMessage(
equalTo("The documented type of the field '[].a' is" + " Object but the actual type is Varies"));
new RequestFieldsSnippet(Arrays.asList(fieldWithPath("[].a").description("one").type(JsonFieldType.OBJECT)))
.document(this.operationBuilder.request("http://localhost").content("[{ \"a\": 5 },{ \"a\": \"b\" }]")
.build());
assertThatExceptionOfType(FieldTypesDoNotMatchException.class)
.isThrownBy(() -> new RequestFieldsSnippet(
Arrays.asList(fieldWithPath("[].a").description("one").type(JsonFieldType.OBJECT)))
.document(this.operationBuilder.request("http://localhost")
.content("[{ \"a\": 5 },{ \"a\": \"b\" }]").build()))
.withMessage("The documented type of the field '[].a' is Object but the actual type is Varies");
}
@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(this.operationBuilder.request("http://localhost").content("<a><b>5</b></a>")
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_XML_VALUE).build());
assertThatExceptionOfType(SnippetException.class)
.isThrownBy(() -> new RequestFieldsSnippet(Collections.<FieldDescriptor>emptyList())
.document(this.operationBuilder.request("http://localhost").content("<a><b>5</b></a>")
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_XML_VALUE).build()))
.withMessageStartingWith("The following parts of the payload were not documented:");
}
@Test
public void xmlDescendentsAreNotDocumentedByFieldDescriptor() throws IOException {
this.thrown.expect(SnippetException.class);
this.thrown.expectMessage(startsWith("The following parts of the payload were not documented:"));
new RequestFieldsSnippet(Arrays.asList(fieldWithPath("a").type("a").description("one")))
.document(this.operationBuilder.request("http://localhost").content("<a><b>5</b></a>")
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_XML_VALUE).build());
assertThatExceptionOfType(SnippetException.class)
.isThrownBy(
() -> new RequestFieldsSnippet(Arrays.asList(fieldWithPath("a").type("a").description("one")))
.document(this.operationBuilder.request("http://localhost").content("<a><b>5</b></a>")
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_XML_VALUE).build()))
.withMessageStartingWith("The following parts of the payload were not documented:");
}
@Test
public void xmlRequestFieldWithNoType() throws IOException {
this.thrown.expect(FieldTypeRequiredException.class);
new RequestFieldsSnippet(Arrays.asList(fieldWithPath("a").description("one")))
.document(this.operationBuilder.request("http://localhost").content("<a>5</a>")
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_XML_VALUE).build());
assertThatExceptionOfType(FieldTypeRequiredException.class)
.isThrownBy(() -> new RequestFieldsSnippet(Arrays.asList(fieldWithPath("a").description("one")))
.document(this.operationBuilder.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(this.operationBuilder.request("http://localhost").content("<a></a>")
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_XML_VALUE).build());
assertThatExceptionOfType(SnippetException.class)
.isThrownBy(() -> new RequestFieldsSnippet(
Arrays.asList(fieldWithPath("a/b").description("one"), fieldWithPath("a").description("one")))
.document(this.operationBuilder.request("http://localhost").content("<a></a>")
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_XML_VALUE).build()))
.withMessage("Fields with the following paths were not found in the payload: [a/b]");
}
@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(this.operationBuilder.request("http://localhost").content("<a><c>5</c></a>")
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_XML_VALUE).build());
assertThatExceptionOfType(SnippetException.class)
.isThrownBy(() -> new RequestFieldsSnippet(Arrays.asList(fieldWithPath("a/b").description("one")))
.document(this.operationBuilder.request("http://localhost").content("<a><c>5</c></a>")
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_XML_VALUE).build()))
.withMessageStartingWith("The following parts of the payload were not documented:")
.withMessageEndingWith("Fields with the following paths were not found in the payload: [a/b]");
}
@Test
public void unsupportedContent() throws IOException {
this.thrown.expect(PayloadHandlingException.class);
this.thrown.expectMessage(
equalTo("Cannot handle text/plain content as it could" + " not be parsed as JSON or XML"));
new RequestFieldsSnippet(Collections.<FieldDescriptor>emptyList())
.document(this.operationBuilder.request("http://localhost").content("Some plain text")
.header(HttpHeaders.CONTENT_TYPE, MediaType.TEXT_PLAIN_VALUE).build());
assertThatExceptionOfType(PayloadHandlingException.class)
.isThrownBy(() -> new RequestFieldsSnippet(Collections.<FieldDescriptor>emptyList())
.document(this.operationBuilder.request("http://localhost").content("Some plain text")
.header(HttpHeaders.CONTENT_TYPE, MediaType.TEXT_PLAIN_VALUE).build()))
.withMessage("Cannot handle text/plain content as it could not be parsed as JSON or XML");
}
@Test
public void nonOptionalFieldBeneathArrayThatIsSometimesNull() throws IOException {
this.thrown.expect(SnippetException.class);
this.thrown.expectMessage(
startsWith("Fields with the following paths were not found in the payload: " + "[a[].b]"));
new RequestFieldsSnippet(Arrays.asList(fieldWithPath("a[].b").description("one").type(JsonFieldType.NUMBER),
fieldWithPath("a[].c").description("two").type(JsonFieldType.NUMBER)))
.document(this.operationBuilder.request("http://localhost").content(
"{\"a\":[{\"b\": 1,\"c\": 2}, " + "{\"b\": null, \"c\": 2}," + " {\"b\": 1,\"c\": 2}]}")
.build());
assertThatExceptionOfType(SnippetException.class)
.isThrownBy(() -> new RequestFieldsSnippet(
Arrays.asList(fieldWithPath("a[].b").description("one").type(JsonFieldType.NUMBER),
fieldWithPath("a[].c").description("two").type(JsonFieldType.NUMBER)))
.document(this.operationBuilder.request("http://localhost")
.content("{\"a\":[{\"b\": 1,\"c\": 2}, " + "{\"b\": null, \"c\": 2},"
+ " {\"b\": 1,\"c\": 2}]}")
.build()))
.withMessageStartingWith("Fields with the following paths were not found in the payload: [a[].b]");
}
@Test
public void nonOptionalFieldBeneathArrayThatIsSometimesAbsent() throws IOException {
this.thrown.expect(SnippetException.class);
this.thrown.expectMessage(
startsWith("Fields with the following paths were not found in the payload: " + "[a[].b]"));
new RequestFieldsSnippet(Arrays.asList(fieldWithPath("a[].b").description("one").type(JsonFieldType.NUMBER),
fieldWithPath("a[].c").description("two").type(JsonFieldType.NUMBER)))
.document(this.operationBuilder.request("http://localhost")
.content("{\"a\":[{\"b\": 1,\"c\": 2}, " + "{\"c\": 2}, {\"b\": 1,\"c\": 2}]}")
.build());
assertThatExceptionOfType(SnippetException.class)
.isThrownBy(() -> new RequestFieldsSnippet(
Arrays.asList(fieldWithPath("a[].b").description("one").type(JsonFieldType.NUMBER),
fieldWithPath("a[].c").description("two").type(JsonFieldType.NUMBER)))
.document(this.operationBuilder.request("http://localhost")
.content("{\"a\":[{\"b\": 1,\"c\": 2}, "
+ "{\"c\": 2}, {\"b\": 1,\"c\": 2}]}")
.build()))
.withMessageStartingWith("Fields with the following paths were not found in the payload: [a[].b]");
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2021 the original author or authors.
* Copyright 2014-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -22,14 +22,12 @@ 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.templates.TemplateFormats;
import org.springframework.restdocs.testfixtures.OperationBuilder;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.startsWith;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath;
/**
@@ -44,32 +42,32 @@ public class RequestPartFieldsSnippetFailureTests {
@Rule
public OperationBuilder operationBuilder = new OperationBuilder(TemplateFormats.asciidoctor());
@Rule
public ExpectedException thrown = ExpectedException.none();
@Test
public void undocumentedRequestPartField() throws IOException {
this.thrown.expect(SnippetException.class);
this.thrown.expectMessage(startsWith("The following parts of the payload were not documented:"));
new RequestPartFieldsSnippet("part", Collections.<FieldDescriptor>emptyList()).document(
this.operationBuilder.request("http://localhost").part("part", "{\"a\": 5}".getBytes()).build());
assertThatExceptionOfType(SnippetException.class)
.isThrownBy(() -> new RequestPartFieldsSnippet("part", Collections.<FieldDescriptor>emptyList())
.document(this.operationBuilder.request("http://localhost")
.part("part", "{\"a\": 5}".getBytes()).build()))
.withMessageStartingWith("The following parts of the payload were not documented:");
}
@Test
public void missingRequestPartField() throws IOException {
this.thrown.expect(SnippetException.class);
this.thrown.expectMessage(startsWith("The following parts of the payload were not documented:"));
new RequestPartFieldsSnippet("part", Arrays.asList(fieldWithPath("b").description("one"))).document(
this.operationBuilder.request("http://localhost").part("part", "{\"a\": 5}".getBytes()).build());
assertThatExceptionOfType(SnippetException.class)
.isThrownBy(
() -> new RequestPartFieldsSnippet("part", Arrays.asList(fieldWithPath("b").description("one")))
.document(this.operationBuilder.request("http://localhost")
.part("part", "{\"a\": 5}".getBytes()).build()))
.withMessageStartingWith("The following parts of the payload were not documented:");
}
@Test
public void missingRequestPart() throws IOException {
this.thrown.expect(SnippetException.class);
this.thrown.expectMessage(equalTo("A request part named 'another' was not found in the request"));
new RequestPartFieldsSnippet("another", Arrays.asList(fieldWithPath("a.b").description("one")))
.document(this.operationBuilder.request("http://localhost")
.part("part", "{\"a\": {\"b\": 5}}".getBytes()).build());
assertThatExceptionOfType(SnippetException.class).isThrownBy(
() -> new RequestPartFieldsSnippet("another", Arrays.asList(fieldWithPath("a.b").description("one")))
.document(this.operationBuilder.request("http://localhost")
.part("part", "{\"a\": {\"b\": 5}}".getBytes()).build()))
.withMessage("A request part named 'another' was not found in the request");
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2021 the original author or authors.
* Copyright 2014-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -22,7 +22,6 @@ 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;
@@ -30,9 +29,7 @@ import org.springframework.restdocs.snippet.SnippetException;
import org.springframework.restdocs.templates.TemplateFormats;
import org.springframework.restdocs.testfixtures.OperationBuilder;
import static org.hamcrest.CoreMatchers.endsWith;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.startsWith;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath;
/**
@@ -46,124 +43,125 @@ public class ResponseFieldsSnippetFailureTests {
@Rule
public OperationBuilder operationBuilder = new OperationBuilder(TemplateFormats.asciidoctor());
@Rule
public ExpectedException thrown = ExpectedException.none();
@Test
public void attemptToDocumentFieldsWithNoResponseBody() throws IOException {
this.thrown.expect(SnippetException.class);
this.thrown.expectMessage(equalTo("Cannot document response fields as the response body is empty"));
new ResponseFieldsSnippet(Arrays.asList(fieldWithPath("a").description("one")))
.document(this.operationBuilder.build());
assertThatExceptionOfType(SnippetException.class)
.isThrownBy(() -> new ResponseFieldsSnippet(Arrays.asList(fieldWithPath("a").description("one")))
.document(this.operationBuilder.build()))
.withMessage("Cannot document response fields as the response body is empty");
}
@Test
public void fieldWithExplicitTypeThatDoesNotMatchThePayload() throws IOException {
this.thrown.expect(FieldTypesDoNotMatchException.class);
this.thrown.expectMessage(
equalTo("The documented type of the field 'a' is" + " Object but the actual type is Number"));
new ResponseFieldsSnippet(Arrays.asList(fieldWithPath("a").description("one").type(JsonFieldType.OBJECT)))
.document(this.operationBuilder.response().content("{ \"a\": 5 }}").build());
assertThatExceptionOfType(FieldTypesDoNotMatchException.class)
.isThrownBy(() -> new ResponseFieldsSnippet(
Arrays.asList(fieldWithPath("a").description("one").type(JsonFieldType.OBJECT)))
.document(this.operationBuilder.response().content("{ \"a\": 5 }}").build()))
.withMessage("The documented type of the field 'a' is Object but the actual type is Number");
}
@Test
public void fieldWithExplicitSpecificTypeThatActuallyVaries() throws IOException {
this.thrown.expect(FieldTypesDoNotMatchException.class);
this.thrown.expectMessage(
equalTo("The documented type of the field '[].a' is" + " Object but the actual type is Varies"));
new ResponseFieldsSnippet(Arrays.asList(fieldWithPath("[].a").description("one").type(JsonFieldType.OBJECT)))
.document(this.operationBuilder.response().content("[{ \"a\": 5 },{ \"a\": \"b\" }]").build());
assertThatExceptionOfType(FieldTypesDoNotMatchException.class)
.isThrownBy(() -> new ResponseFieldsSnippet(
Arrays.asList(fieldWithPath("[].a").description("one").type(JsonFieldType.OBJECT))).document(
this.operationBuilder.response().content("[{ \"a\": 5 },{ \"a\": \"b\" }]").build()))
.withMessage("The documented type of the field '[].a' is Object but the actual type is Varies");
}
@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(this.operationBuilder.response()
.content("<a><b>5</b></a>").header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_XML_VALUE).build());
assertThatExceptionOfType(SnippetException.class)
.isThrownBy(() -> new ResponseFieldsSnippet(Collections.<FieldDescriptor>emptyList())
.document(this.operationBuilder.response().content("<a><b>5</b></a>")
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_XML_VALUE).build()))
.withMessageStartingWith("The following parts of the payload were not documented:");
}
@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(this.operationBuilder.response().content("<a>foo</a>")
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_XML_VALUE).build());
assertThatExceptionOfType(SnippetException.class)
.isThrownBy(
() -> new ResponseFieldsSnippet(Arrays.asList(fieldWithPath("a").description("one").type("b"),
fieldWithPath("a/@id").description("two").type("c")))
.document(
this.operationBuilder.response().content("<a>foo</a>")
.header(HttpHeaders.CONTENT_TYPE,
MediaType.APPLICATION_XML_VALUE)
.build()))
.withMessage("Fields with the following paths were not found in the payload: [a/@id]");
}
@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(this.operationBuilder.response().content("<a id=\"foo\">bar</a>")
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_XML_VALUE).build());
assertThatExceptionOfType(SnippetException.class).isThrownBy(
() -> new ResponseFieldsSnippet(Arrays.asList(fieldWithPath("a/@id").description("one").type("a")))
.document(this.operationBuilder.response().content("<a id=\"foo\">bar</a>")
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_XML_VALUE).build()))
.withMessage(String.format("The following parts of the payload were not documented:%n<a>bar</a>%n"));
}
@Test
public void xmlResponseFieldWithNoType() throws IOException {
this.thrown.expect(FieldTypeRequiredException.class);
new ResponseFieldsSnippet(Arrays.asList(fieldWithPath("a").description("one")))
.document(this.operationBuilder.response().content("<a>5</a>")
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_XML_VALUE).build());
assertThatExceptionOfType(FieldTypeRequiredException.class)
.isThrownBy(() -> new ResponseFieldsSnippet(Arrays.asList(fieldWithPath("a").description("one")))
.document(this.operationBuilder.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(this.operationBuilder.response().content("<a></a>")
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_XML_VALUE).build());
assertThatExceptionOfType(SnippetException.class)
.isThrownBy(() -> new ResponseFieldsSnippet(
Arrays.asList(fieldWithPath("a/b").description("one"), fieldWithPath("a").description("one")))
.document(this.operationBuilder.response().content("<a></a>")
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_XML_VALUE).build()))
.withMessage("Fields with the following paths were not found in the payload: [a/b]");
}
@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(this.operationBuilder.response().content("<a><c>5</c></a>")
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_XML_VALUE).build());
assertThatExceptionOfType(SnippetException.class)
.isThrownBy(() -> new ResponseFieldsSnippet(Arrays.asList(fieldWithPath("a/b").description("one")))
.document(this.operationBuilder.response().content("<a><c>5</c></a>")
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_XML_VALUE).build()))
.withMessageStartingWith("The following parts of the payload were not documented:")
.withMessageEndingWith("Fields with the following paths were not found in the payload: [a/b]");
}
@Test
public void unsupportedContent() throws IOException {
this.thrown.expect(PayloadHandlingException.class);
this.thrown.expectMessage(
equalTo("Cannot handle text/plain content as it could" + " not be parsed as JSON or XML"));
new ResponseFieldsSnippet(Collections.<FieldDescriptor>emptyList()).document(this.operationBuilder.response()
.content("Some plain text").header(HttpHeaders.CONTENT_TYPE, MediaType.TEXT_PLAIN_VALUE).build());
assertThatExceptionOfType(PayloadHandlingException.class)
.isThrownBy(() -> new ResponseFieldsSnippet(Collections.<FieldDescriptor>emptyList())
.document(this.operationBuilder.response().content("Some plain text")
.header(HttpHeaders.CONTENT_TYPE, MediaType.TEXT_PLAIN_VALUE).build()))
.withMessage("Cannot handle text/plain content as it could not be parsed as JSON or XML");
}
@Test
public void nonOptionalFieldBeneathArrayThatIsSometimesNull() throws IOException {
this.thrown.expect(SnippetException.class);
this.thrown.expectMessage(
startsWith("Fields with the following paths were not found in the payload: " + "[a[].b]"));
new ResponseFieldsSnippet(Arrays.asList(fieldWithPath("a[].b").description("one").type(JsonFieldType.NUMBER),
fieldWithPath("a[].c").description("two").type(JsonFieldType.NUMBER)))
.document(this.operationBuilder.response().content(
"{\"a\":[{\"b\": 1,\"c\": 2}, " + "{\"b\": null, \"c\": 2}," + " {\"b\": 1,\"c\": 2}]}")
.build());
assertThatExceptionOfType(SnippetException.class)
.isThrownBy(() -> new ResponseFieldsSnippet(
Arrays.asList(fieldWithPath("a[].b").description("one").type(JsonFieldType.NUMBER),
fieldWithPath("a[].c").description("two").type(JsonFieldType.NUMBER)))
.document(this.operationBuilder.response()
.content("{\"a\":[{\"b\": 1,\"c\": 2}, " + "{\"b\": null, \"c\": 2},"
+ " {\"b\": 1,\"c\": 2}]}")
.build()))
.withMessageStartingWith("Fields with the following paths were not found in the payload: [a[].b]");
}
@Test
public void nonOptionalFieldBeneathArrayThatIsSometimesAbsent() throws IOException {
this.thrown.expect(SnippetException.class);
this.thrown.expectMessage(
startsWith("Fields with the following paths were not found in the payload: " + "[a[].b]"));
new ResponseFieldsSnippet(Arrays.asList(fieldWithPath("a[].b").description("one").type(JsonFieldType.NUMBER),
fieldWithPath("a[].c").description("two").type(JsonFieldType.NUMBER)))
.document(this.operationBuilder.response()
.content("{\"a\":[{\"b\": 1,\"c\": 2}, " + "{\"c\": 2}, {\"b\": 1,\"c\": 2}]}")
.build());
assertThatExceptionOfType(SnippetException.class)
.isThrownBy(() -> new ResponseFieldsSnippet(
Arrays.asList(fieldWithPath("a[].b").description("one").type(JsonFieldType.NUMBER),
fieldWithPath("a[].c").description("two").type(JsonFieldType.NUMBER)))
.document(this.operationBuilder.response()
.content("{\"a\":[{\"b\": 1,\"c\": 2}, "
+ "{\"c\": 2}, {\"b\": 1,\"c\": 2}]}")
.build()))
.withMessageStartingWith("Fields with the following paths were not found in the payload: [a[].b]");
}
}

View File

@@ -22,14 +22,13 @@ import java.util.Collections;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.springframework.restdocs.generate.RestDocumentationGenerator;
import org.springframework.restdocs.snippet.SnippetException;
import org.springframework.restdocs.templates.TemplateFormats;
import org.springframework.restdocs.testfixtures.OperationBuilder;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName;
/**
@@ -43,35 +42,33 @@ public class PathParametersSnippetFailureTests {
@Rule
public OperationBuilder operationBuilder = new OperationBuilder(TemplateFormats.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(this.operationBuilder
.attribute(RestDocumentationGenerator.ATTRIBUTE_NAME_URL_TEMPLATE, "/{a}/").build());
assertThatExceptionOfType(SnippetException.class)
.isThrownBy(() -> new PathParametersSnippet(Collections.<ParameterDescriptor>emptyList())
.document(this.operationBuilder
.attribute(RestDocumentationGenerator.ATTRIBUTE_NAME_URL_TEMPLATE, "/{a}/").build()))
.withMessage("Path parameters with the following names were not documented: [a]");
}
@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(
this.operationBuilder.attribute(RestDocumentationGenerator.ATTRIBUTE_NAME_URL_TEMPLATE, "/").build());
assertThatExceptionOfType(SnippetException.class)
.isThrownBy(() -> new PathParametersSnippet(Arrays.asList(parameterWithName("a").description("one")))
.document(this.operationBuilder
.attribute(RestDocumentationGenerator.ATTRIBUTE_NAME_URL_TEMPLATE, "/").build()))
.withMessage("Path parameters with the following names were not found in the request: [a]");
}
@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(this.operationBuilder
.attribute(RestDocumentationGenerator.ATTRIBUTE_NAME_URL_TEMPLATE, "/{b}").build());
assertThatExceptionOfType(SnippetException.class)
.isThrownBy(() -> new PathParametersSnippet(Arrays.asList(parameterWithName("a").description("one")))
.document(this.operationBuilder
.attribute(RestDocumentationGenerator.ATTRIBUTE_NAME_URL_TEMPLATE, "/{b}").build()))
.withMessage(
"Path parameters with the following names were not documented: [b]. Path parameters with the"
+ " following names were not found in the request: [a]");
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2021 the original author or authors.
* Copyright 2014-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -22,13 +22,12 @@ 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.templates.TemplateFormats;
import org.springframework.restdocs.testfixtures.OperationBuilder;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName;
/**
@@ -42,34 +41,29 @@ public class RequestParametersSnippetFailureTests {
@Rule
public OperationBuilder operationBuilder = new OperationBuilder(TemplateFormats.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(this.operationBuilder.request("http://localhost").param("a", "alpha").build());
assertThatExceptionOfType(SnippetException.class)
.isThrownBy(() -> new RequestParametersSnippet(Collections.<ParameterDescriptor>emptyList())
.document(this.operationBuilder.request("http://localhost").param("a", "alpha").build()))
.withMessage("Request parameters with the following names were not documented: [a]");
}
@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(this.operationBuilder.request("http://localhost").build());
assertThatExceptionOfType(SnippetException.class)
.isThrownBy(() -> new RequestParametersSnippet(Arrays.asList(parameterWithName("a").description("one")))
.document(this.operationBuilder.request("http://localhost").build()))
.withMessage("Request parameters with the following names were not found in the request: [a]");
}
@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(this.operationBuilder.request("http://localhost").param("b", "bravo").build());
assertThatExceptionOfType(SnippetException.class)
.isThrownBy(() -> new RequestParametersSnippet(Arrays.asList(parameterWithName("a").description("one")))
.document(this.operationBuilder.request("http://localhost").param("b", "bravo").build()))
.withMessage("Request parameters with the following names were not documented: [b]. Request parameters"
+ " with the following names were not found in the request: [a]");
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2021 the original author or authors.
* Copyright 2014-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -22,13 +22,12 @@ 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.templates.TemplateFormats;
import org.springframework.restdocs.testfixtures.OperationBuilder;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.springframework.restdocs.request.RequestDocumentation.partWithName;
/**
@@ -42,34 +41,29 @@ public class RequestPartsSnippetFailureTests {
@Rule
public OperationBuilder operationBuilder = new OperationBuilder(TemplateFormats.asciidoctor());
@Rule
public ExpectedException thrown = ExpectedException.none();
@Test
public void undocumentedPart() throws IOException {
this.thrown.expect(SnippetException.class);
this.thrown.expectMessage(equalTo("Request parts with the following names were" + " not documented: [a]"));
new RequestPartsSnippet(Collections.<RequestPartDescriptor>emptyList())
.document(this.operationBuilder.request("http://localhost").part("a", "alpha".getBytes()).build());
assertThatExceptionOfType(SnippetException.class)
.isThrownBy(() -> new RequestPartsSnippet(Collections.<RequestPartDescriptor>emptyList()).document(
this.operationBuilder.request("http://localhost").part("a", "alpha".getBytes()).build()))
.withMessage("Request parts with the following names were not documented: [a]");
}
@Test
public void missingPart() throws IOException {
this.thrown.expect(SnippetException.class);
this.thrown.expectMessage(
equalTo("Request parts with the following names were" + " not found in the request: [a]"));
new RequestPartsSnippet(Arrays.asList(partWithName("a").description("one")))
.document(this.operationBuilder.request("http://localhost").build());
assertThatExceptionOfType(SnippetException.class)
.isThrownBy(() -> new RequestPartsSnippet(Arrays.asList(partWithName("a").description("one")))
.document(this.operationBuilder.request("http://localhost").build()))
.withMessage("Request parts with the following names were not found in the request: [a]");
}
@Test
public void undocumentedAndMissingParts() throws IOException {
this.thrown.expect(SnippetException.class);
this.thrown.expectMessage(equalTo(
"Request parts with the following names were" + " not documented: [b]. Request parts with the following"
+ " names were not found in the request: [a]"));
new RequestPartsSnippet(Arrays.asList(partWithName("a").description("one")))
.document(this.operationBuilder.request("http://localhost").part("b", "bravo".getBytes()).build());
assertThatExceptionOfType(SnippetException.class)
.isThrownBy(() -> new RequestPartsSnippet(Arrays.asList(partWithName("a").description("one"))).document(
this.operationBuilder.request("http://localhost").part("b", "bravo".getBytes()).build()))
.withMessage("Request parts with the following names were not documented: [b]. Request parts with the"
+ " following names were not found in the request: [a]");
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2019 the original author or authors.
* Copyright 2014-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -21,14 +21,12 @@ import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.Callable;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.springframework.core.io.Resource;
import static org.assertj.core.api.Assertions.assertThat;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
/**
* Tests for {@link TemplateResourceResolver}.
@@ -37,9 +35,6 @@ import static org.hamcrest.CoreMatchers.equalTo;
*/
public class StandardTemplateResourceResolverTests {
@Rule
public final ExpectedException thrown = ExpectedException.none();
private final TemplateResourceResolver resolver = new StandardTemplateResourceResolver(
TemplateFormats.asciidoctor());
@@ -101,16 +96,10 @@ public class StandardTemplateResourceResolverTests {
@Test
public void failsIfCustomAndDefaultSnippetsDoNotExist() throws Exception {
this.thrown.expect(IllegalStateException.class);
this.thrown.expectMessage(equalTo("Template named 'test' could not be resolved"));
doWithThreadContextClassLoader(this.classLoader, new Callable<Resource>() {
@Override
public Resource call() {
return StandardTemplateResourceResolverTests.this.resolver.resolveTemplateResource("test");
}
});
assertThatIllegalStateException()
.isThrownBy(() -> doWithThreadContextClassLoader(this.classLoader,
() -> StandardTemplateResourceResolverTests.this.resolver.resolveTemplateResource("test")))
.withMessage("Template named 'test' could not be resolved");
}
private <T> T doWithThreadContextClassLoader(ClassLoader classLoader, Callable<T> action) throws Exception {

View File

@@ -0,0 +1,68 @@
/*
* Copyright 2014-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.restdocs.testfixtures;
/**
* Provides access to {@link System#out System.out} and {@link System#err System.err}
* output that has been captured by the {@link OutputCaptureRule}. Can be used to apply
* assertions using AssertJ. For example: <pre class="code">
* assertThat(output).contains("started"); // Checks all output
* assertThat(output.getErr()).contains("failed"); // Only checks System.err
* assertThat(output.getOut()).contains("ok"); // Only checks System.out
* </pre>
*
* @author Madhura Bhave
* @author Phillip Webb
* @author Andy Wilkinson
*/
public interface CapturedOutput extends CharSequence {
@Override
default int length() {
return toString().length();
}
@Override
default char charAt(int index) {
return toString().charAt(index);
}
@Override
default CharSequence subSequence(int start, int end) {
return toString().subSequence(start, end);
}
/**
* Return all content (both {@link System#out System.out} and {@link System#err
* System.err}) in the order that it was captured.
* @return all captured output
*/
String getAll();
/**
* Return {@link System#out System.out} content in the order that it was captured.
* @return {@link System#out System.out} captured output
*/
String getOut();
/**
* Return {@link System#err System.err} content in the order that it was captured.
* @return {@link System#err System.err} captured output
*/
String getErr();
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2021 the original author or authors.
* Copyright 2014-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,139 +16,257 @@
package org.springframework.restdocs.testfixtures;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintStream;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Deque;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.Predicate;
import org.assertj.core.api.HamcrestCondition;
import org.hamcrest.Matcher;
import org.junit.rules.TestRule;
import org.junit.runner.Description;
import org.junit.runners.model.Statement;
import static org.assertj.core.api.Assertions.assertThat;
import static org.hamcrest.Matchers.allOf;
import org.springframework.util.Assert;
/**
* JUnit {@code @Rule} to capture output from System.out and System.err.
* Provides support for capturing {@link System#out System.out} and {@link System#err
* System.err}.
*
* @author Madhura Bhave
* @author Phillip Webb
* @author Andy Wilkinson
* @author Sam Brannen
* @see OutputCaptureRule
*/
public class OutputCapture implements TestRule {
class OutputCapture implements CapturedOutput {
private CaptureOutputStream captureOut;
private final Deque<SystemCapture> systemCaptures = new ArrayDeque<>();
private CaptureOutputStream captureErr;
private ByteArrayOutputStream capturedOutput;
private List<Matcher<? super String>> matchers = new ArrayList<>();
@Override
public Statement apply(final Statement base, Description description) {
return new Statement() {
@Override
public void evaluate() throws Throwable {
captureOutput();
try {
base.evaluate();
}
finally {
try {
if (!OutputCapture.this.matchers.isEmpty()) {
assertThat(getOutputAsString())
.is(new HamcrestCondition<>(allOf(OutputCapture.this.matchers)));
}
}
finally {
releaseOutput();
}
}
}
};
}
private void captureOutput() {
this.capturedOutput = new ByteArrayOutputStream();
this.captureOut = new CaptureOutputStream(System.out, this.capturedOutput);
this.captureErr = new CaptureOutputStream(System.err, this.capturedOutput);
System.setOut(new PrintStream(this.captureOut));
System.setErr(new PrintStream(this.captureErr));
}
private String getOutputAsString() {
flush();
return this.capturedOutput.toString();
}
private void releaseOutput() {
System.setOut(this.captureOut.getOriginal());
System.setErr(this.captureErr.getOriginal());
this.capturedOutput = null;
}
private void flush() {
try {
this.captureOut.flush();
this.captureErr.flush();
}
catch (IOException ex) {
// ignore
}
/**
* Push a new system capture session onto the stack.
*/
final void push() {
this.systemCaptures.addLast(new SystemCapture());
}
/**
* Verify that the output is matched by the supplied {@code matcher}. Verification is
* performed after the test method has executed.
* @param matcher the matcher
* Pop the last system capture session from the stack.
*/
public final void expect(Matcher<? super String> matcher) {
this.matchers.add(matcher);
final void pop() {
this.systemCaptures.removeLast().release();
}
private static final class CaptureOutputStream extends OutputStream {
@Override
public boolean equals(Object obj) {
if (obj == this) {
return true;
}
if (obj instanceof CapturedOutput || obj instanceof CharSequence) {
return getAll().equals(obj.toString());
}
return false;
}
private final PrintStream original;
@Override
public int hashCode() {
return toString().hashCode();
}
private final OutputStream copy;
@Override
public String toString() {
return getAll();
}
private CaptureOutputStream(PrintStream original, OutputStream copy) {
this.original = original;
/**
* Return all content (both {@link System#out System.out} and {@link System#err
* System.err}) in the order that it was captured.
* @return all captured output
*/
@Override
public String getAll() {
return get((type) -> true);
}
/**
* Return {@link System#out System.out} content in the order that it was captured.
* @return {@link System#out System.out} captured output
*/
@Override
public String getOut() {
return get(Type.OUT::equals);
}
/**
* Return {@link System#err System.err} content in the order that it was captured.
* @return {@link System#err System.err} captured output
*/
@Override
public String getErr() {
return get(Type.ERR::equals);
}
/**
* Resets the current capture session, clearing its captured output.
*/
void reset() {
this.systemCaptures.peek().reset();
}
private String get(Predicate<Type> filter) {
Assert.state(!this.systemCaptures.isEmpty(),
"No system captures found. Please check your output capture registration.");
StringBuilder builder = new StringBuilder();
for (SystemCapture systemCapture : this.systemCaptures) {
systemCapture.append(builder, filter);
}
return builder.toString();
}
/**
* A capture session that captures {@link System#out System.out} and {@link System#out
* System.err}.
*/
private static class SystemCapture {
private final Object monitor = new Object();
private final PrintStreamCapture out;
private final PrintStreamCapture err;
private final List<CapturedString> capturedStrings = new ArrayList<>();
SystemCapture() {
this.out = new PrintStreamCapture(System.out, this::captureOut);
this.err = new PrintStreamCapture(System.err, this::captureErr);
System.setOut(this.out);
System.setErr(this.err);
}
void release() {
System.setOut(this.out.getParent());
System.setErr(this.err.getParent());
}
private void captureOut(String string) {
synchronized (this.monitor) {
this.capturedStrings.add(new CapturedString(Type.OUT, string));
}
}
private void captureErr(String string) {
synchronized (this.monitor) {
this.capturedStrings.add(new CapturedString(Type.ERR, string));
}
}
void append(StringBuilder builder, Predicate<Type> filter) {
synchronized (this.monitor) {
for (CapturedString stringCapture : this.capturedStrings) {
if (filter.test(stringCapture.getType())) {
builder.append(stringCapture);
}
}
}
}
void reset() {
synchronized (this.monitor) {
this.capturedStrings.clear();
}
}
}
/**
* A {@link PrintStream} implementation that captures written strings.
*/
private static class PrintStreamCapture extends PrintStream {
private final PrintStream parent;
PrintStreamCapture(PrintStream parent, Consumer<String> copy) {
super(new OutputStreamCapture(getSystemStream(parent), copy));
this.parent = parent;
}
PrintStream getParent() {
return this.parent;
}
private static PrintStream getSystemStream(PrintStream printStream) {
while (printStream instanceof PrintStreamCapture) {
printStream = ((PrintStreamCapture) printStream).getParent();
}
return printStream;
}
}
/**
* An {@link OutputStream} implementation that captures written strings.
*/
private static class OutputStreamCapture extends OutputStream {
private final PrintStream systemStream;
private final Consumer<String> copy;
OutputStreamCapture(PrintStream systemStream, Consumer<String> copy) {
this.systemStream = systemStream;
this.copy = copy;
}
@Override
public void write(int b) throws IOException {
this.copy.write(b);
this.original.write(b);
this.original.flush();
}
@Override
public void write(byte[] b) throws IOException {
write(b, 0, b.length);
write(new byte[] { (byte) (b & 0xFF) });
}
@Override
public void write(byte[] b, int off, int len) throws IOException {
this.copy.write(b, off, len);
this.original.write(b, off, len);
}
private PrintStream getOriginal() {
return this.original;
this.copy.accept(new String(b, off, len));
this.systemStream.write(b, off, len);
}
@Override
public void flush() throws IOException {
this.copy.flush();
this.original.flush();
this.systemStream.flush();
}
}
/**
* A captured string that forms part of the full output.
*/
private static class CapturedString {
private final Type type;
private final String string;
CapturedString(Type type, String string) {
this.type = type;
this.string = string;
}
Type getType() {
return this.type;
}
@Override
public String toString() {
return this.string;
}
}
/**
* Types of content that can be captured.
*/
private enum Type {
OUT, ERR
}
}

View File

@@ -0,0 +1,86 @@
/*
* Copyright 2014-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.restdocs.testfixtures;
import org.junit.Rule;
import org.junit.rules.TestRule;
import org.junit.runner.Description;
import org.junit.runners.model.Statement;
/**
* JUnit {@code @Rule} to capture output from System.out and System.err.
* <p>
* To use add as a {@link Rule @Rule}:
*
* <pre class="code">
* public class MyTest {
*
* &#064;Rule
* public OutputCaptureRule output = new OutputCaptureRule();
*
* &#064;Test
* public void test() {
* assertThat(output).contains("ok");
* }
*
* }
* </pre>
*
* @author Phillip Webb
* @author Andy Wilkinson
*/
public class OutputCaptureRule implements TestRule, CapturedOutput {
private final OutputCapture delegate = new OutputCapture();
@Override
public Statement apply(Statement base, Description description) {
return new Statement() {
@Override
public void evaluate() throws Throwable {
OutputCaptureRule.this.delegate.push();
try {
base.evaluate();
}
finally {
OutputCaptureRule.this.delegate.pop();
}
}
};
}
@Override
public String getAll() {
return this.delegate.getAll();
}
@Override
public String getOut() {
return this.delegate.getOut();
}
@Override
public String getErr() {
return this.delegate.getErr();
}
@Override
public String toString() {
return this.delegate.toString();
}
}