Migrate tests to JUnit 5

Closes gh-959
This commit is contained in:
Andy Wilkinson
2025-06-03 16:47:20 +01:00
parent d0c224de95
commit c7bde714d6
105 changed files with 4310 additions and 4163 deletions

View File

@@ -10,10 +10,15 @@ dependencies {
internal(platform(project(":spring-restdocs-platform")))
testImplementation("junit:junit")
testImplementation("org.apache.pdfbox:pdfbox")
testImplementation("org.assertj:assertj-core")
testImplementation("org.junit.jupiter:junit-jupiter")
testImplementation("org.springframework:spring-core")
testRuntimeOnly("org.asciidoctor:asciidoctorj-pdf")
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
}
tasks.named("test") {
useJUnitPlatform()
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2023 the original author or authors.
* Copyright 2014-2025 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.
@@ -35,11 +35,10 @@ import org.asciidoctor.Asciidoctor;
import org.asciidoctor.Attributes;
import org.asciidoctor.Options;
import org.asciidoctor.SafeMode;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import org.springframework.util.FileSystemUtils;
@@ -51,42 +50,42 @@ import static org.assertj.core.api.Assertions.assertThat;
* @author Gerrit Meier
* @author Andy Wilkinson
*/
public abstract class AbstractOperationBlockMacroTests {
@Rule
public TemporaryFolder temp = new TemporaryFolder();
private Options options;
abstract class AbstractOperationBlockMacroTests {
private final Asciidoctor asciidoctor = Asciidoctor.Factory.create();
@Before
public void setUp() throws IOException {
@TempDir
protected File temp;
private Options options;
@BeforeEach
void setUp() throws IOException {
prepareOperationSnippets(getBuildOutputLocation());
this.options = Options.builder().safe(SafeMode.UNSAFE).baseDir(getSourceLocation()).build();
this.options.setAttributes(getAttributes());
CapturingLogHandler.clear();
}
@After
public void verifyLogging() {
@AfterEach
void verifyLogging() {
assertThat(CapturingLogHandler.getLogRecords()).isEmpty();
}
public void prepareOperationSnippets(File buildOutputLocation) throws IOException {
private void prepareOperationSnippets(File buildOutputLocation) throws IOException {
File destination = new File(buildOutputLocation, "generated-snippets/some-operation");
destination.mkdirs();
FileSystemUtils.copyRecursively(new File("src/test/resources/some-operation"), destination);
}
@Test
public void codeBlockSnippetInclude() throws Exception {
void codeBlockSnippetInclude() throws Exception {
String result = this.asciidoctor.convert("operation::some-operation[snippets='curl-request']", this.options);
assertThat(result).isEqualTo(getExpectedContentFromFile("snippet-simple"));
}
@Test
public void operationWithParameterizedName() throws Exception {
void operationWithParameterizedName() throws Exception {
Attributes attributes = getAttributes();
attributes.setAttribute("name", "some");
this.options.setAttributes(attributes);
@@ -95,20 +94,20 @@ public abstract class AbstractOperationBlockMacroTests {
}
@Test
public void codeBlockSnippetIncludeWithPdfBackend() throws Exception {
void codeBlockSnippetIncludeWithPdfBackend() throws Exception {
File output = configurePdfOutput();
this.asciidoctor.convert("operation::some-operation[snippets='curl-request']", this.options);
assertThat(extractStrings(output)).containsExactly("Curl request", "$ curl 'http://localhost:8080/' -i", "1");
}
@Test
public void tableSnippetInclude() throws Exception {
void tableSnippetInclude() throws Exception {
String result = this.asciidoctor.convert("operation::some-operation[snippets='response-fields']", this.options);
assertThat(result).isEqualTo(getExpectedContentFromFile("snippet-table"));
}
@Test
public void tableSnippetIncludeWithPdfBackend() throws Exception {
void tableSnippetIncludeWithPdfBackend() throws Exception {
File output = configurePdfOutput();
this.asciidoctor.convert("operation::some-operation[snippets='response-fields']", this.options);
assertThat(extractStrings(output)).containsExactly("Response fields", "Path", "Type", "Description", "a",
@@ -116,14 +115,14 @@ public abstract class AbstractOperationBlockMacroTests {
}
@Test
public void includeSnippetInSection() throws Exception {
void includeSnippetInSection() throws Exception {
String result = this.asciidoctor.convert("= A\n:doctype: book\n:sectnums:\n\nAlpha\n\n== B\n\nBravo\n\n"
+ "operation::some-operation[snippets='curl-request']\n\n== C\n", this.options);
assertThat(result).isEqualTo(getExpectedContentFromFile("snippet-in-section"));
}
@Test
public void includeSnippetInSectionWithAbsoluteLevelOffset() throws Exception {
void includeSnippetInSectionWithAbsoluteLevelOffset() throws Exception {
String result = this.asciidoctor
.convert("= A\n:doctype: book\n:sectnums:\n:leveloffset: 1\n\nAlpha\n\n= B\n\nBravo\n\n"
+ "operation::some-operation[snippets='curl-request']\n\n= C\n", this.options);
@@ -131,7 +130,7 @@ public abstract class AbstractOperationBlockMacroTests {
}
@Test
public void includeSnippetInSectionWithRelativeLevelOffset() throws Exception {
void includeSnippetInSectionWithRelativeLevelOffset() throws Exception {
String result = this.asciidoctor
.convert("= A\n:doctype: book\n:sectnums:\n:leveloffset: +1\n\nAlpha\n\n= B\n\nBravo\n\n"
+ "operation::some-operation[snippets='curl-request']\n\n= C\n", this.options);
@@ -139,7 +138,7 @@ public abstract class AbstractOperationBlockMacroTests {
}
@Test
public void includeSnippetInSectionWithPdfBackend() throws Exception {
void includeSnippetInSectionWithPdfBackend() throws Exception {
File output = configurePdfOutput();
this.asciidoctor.convert("== Section\n" + "operation::some-operation[snippets='curl-request']", this.options);
assertThat(extractStrings(output)).containsExactly("Section", "Curl request",
@@ -147,26 +146,26 @@ public abstract class AbstractOperationBlockMacroTests {
}
@Test
public void includeMultipleSnippets() throws Exception {
void includeMultipleSnippets() throws Exception {
String result = this.asciidoctor.convert("operation::some-operation[snippets='curl-request,http-request']",
this.options);
assertThat(result).isEqualTo(getExpectedContentFromFile("multiple-snippets"));
}
@Test
public void useMacroWithoutSnippetAttributeAddsAllSnippets() throws Exception {
void useMacroWithoutSnippetAttributeAddsAllSnippets() throws Exception {
String result = this.asciidoctor.convert("operation::some-operation[]", this.options);
assertThat(result).isEqualTo(getExpectedContentFromFile("all-snippets"));
}
@Test
public void useMacroWithEmptySnippetAttributeAddsAllSnippets() throws Exception {
void useMacroWithEmptySnippetAttributeAddsAllSnippets() throws Exception {
String result = this.asciidoctor.convert("operation::some-operation[snippets=]", this.options);
assertThat(result).isEqualTo(getExpectedContentFromFile("all-snippets"));
}
@Test
public void includingMissingSnippetAddsWarning() throws Exception {
void includingMissingSnippetAddsWarning() throws Exception {
String result = this.asciidoctor.convert("operation::some-operation[snippets='missing-snippet']", this.options);
assertThat(result).startsWith(getExpectedContentFromFile("missing-snippet"));
assertThat(CapturingLogHandler.getLogRecords()).hasSize(1);
@@ -177,13 +176,13 @@ public abstract class AbstractOperationBlockMacroTests {
}
@Test
public void defaultTitleIsProvidedForCustomSnippet() throws Exception {
void defaultTitleIsProvidedForCustomSnippet() throws Exception {
String result = this.asciidoctor.convert("operation::some-operation[snippets='custom-snippet']", this.options);
assertThat(result).isEqualTo(getExpectedContentFromFile("custom-snippet-default-title"));
}
@Test
public void missingOperationIsHandledGracefully() throws Exception {
void missingOperationIsHandledGracefully() throws Exception {
String result = this.asciidoctor.convert("operation::missing-operation[]", this.options);
assertThat(result).startsWith(getExpectedContentFromFile("missing-operation"));
assertThat(CapturingLogHandler.getLogRecords()).hasSize(1);
@@ -194,14 +193,14 @@ public abstract class AbstractOperationBlockMacroTests {
}
@Test
public void titleOfBuiltInSnippetCanBeCustomizedUsingDocumentAttribute() throws URISyntaxException, IOException {
void titleOfBuiltInSnippetCanBeCustomizedUsingDocumentAttribute() throws URISyntaxException, IOException {
String result = this.asciidoctor.convert(":operation-curl-request-title: Example request\n"
+ "operation::some-operation[snippets='curl-request']", this.options);
assertThat(result).isEqualTo(getExpectedContentFromFile("built-in-snippet-custom-title"));
}
@Test
public void titleOfCustomSnippetCanBeCustomizedUsingDocumentAttribute() throws Exception {
void titleOfCustomSnippetCanBeCustomizedUsingDocumentAttribute() throws Exception {
String result = this.asciidoctor.convert(":operation-custom-snippet-title: Customized title\n"
+ "operation::some-operation[snippets='custom-snippet']", this.options);
assertThat(result).isEqualTo(getExpectedContentFromFile("custom-snippet-custom-title"));

View File

@@ -21,7 +21,7 @@ import java.io.File;
import org.asciidoctor.Asciidoctor;
import org.asciidoctor.Attributes;
import org.asciidoctor.Options;
import org.junit.Test;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
@@ -30,23 +30,23 @@ import static org.assertj.core.api.Assertions.assertThat;
*
* @author Andy Wilkinson
*/
public class DefaultAttributesPreprocessorTests {
class DefaultAttributesPreprocessorTests {
@Test
public void snippetsAttributeIsSet() {
void snippetsAttributeIsSet() {
String converted = createAsciidoctor().convert("{snippets}", createOptions("projectdir=../../.."));
assertThat(converted).contains("build" + File.separatorChar + "generated-snippets");
}
@Test
public void snippetsAttributeFromConvertArgumentIsNotOverridden() {
void snippetsAttributeFromConvertArgumentIsNotOverridden() {
String converted = createAsciidoctor().convert("{snippets}",
createOptions("snippets=custom projectdir=../../.."));
assertThat(converted).contains("custom");
}
@Test
public void snippetsAttributeFromDocumentPreambleIsNotOverridden() {
void snippetsAttributeFromDocumentPreambleIsNotOverridden() {
String converted = createAsciidoctor().convert(":snippets: custom\n{snippets}",
createOptions("projectdir=../../.."));
assertThat(converted).contains("custom");

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2023 the original author or authors.
* Copyright 2014-2025 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,47 +19,42 @@ package org.springframework.restdocs.asciidoctor;
import java.io.File;
import org.asciidoctor.Attributes;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
import org.junit.jupiter.params.ParameterizedClass;
import org.junit.jupiter.params.provider.ValueSource;
/**
* Tests for Ruby operation block macro when used in a Gradle build.
*
* @author Andy Wilkinson
*/
@RunWith(Parameterized.class)
public class GradleOperationBlockMacroTests extends AbstractOperationBlockMacroTests {
@ParameterizedClass
@ValueSource(strings = { "projectdir", "gradle-projectdir" })
class GradleOperationBlockMacroTests extends AbstractOperationBlockMacroTests {
private final String attributeName;
public GradleOperationBlockMacroTests(String attributeName) {
GradleOperationBlockMacroTests(String attributeName) {
this.attributeName = attributeName;
}
@Parameters(name = "{0}")
public static Object[] parameters() {
return new Object[] { "projectdir", "gradle-projectdir" };
}
@Override
protected Attributes getAttributes() {
Attributes attributes = Attributes.builder()
.attribute(this.attributeName, new File(this.temp.getRoot(), "gradle-project").getAbsolutePath())
.attribute(this.attributeName, new File(this.temp, "gradle-project").getAbsolutePath())
.build();
return attributes;
}
@Override
protected File getBuildOutputLocation() {
File outputLocation = new File(this.temp.getRoot(), "gradle-project/build");
File outputLocation = new File(this.temp, "gradle-project/build");
outputLocation.mkdirs();
return outputLocation;
}
@Override
protected File getSourceLocation() {
File sourceLocation = new File(this.temp.getRoot(), "gradle-project/src/docs/asciidoc");
File sourceLocation = new File(this.temp, "gradle-project/src/docs/asciidoc");
if (!sourceLocation.exists()) {
sourceLocation.mkdirs();
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2021 the original author or authors.
* Copyright 2014-2025 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.
@@ -20,23 +20,23 @@ import java.io.File;
import java.io.IOException;
import org.asciidoctor.Attributes;
import org.junit.After;
import org.junit.Before;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
/**
* Tests for Ruby operation block macro when used in a Maven build.
*
* @author Andy Wilkinson
*/
public class MavenOperationBlockMacroTests extends AbstractOperationBlockMacroTests {
class MavenOperationBlockMacroTests extends AbstractOperationBlockMacroTests {
@Before
public void setMavenHome() {
@BeforeEach
void setMavenHome() {
System.setProperty("maven.home", "maven-home");
}
@After
public void clearMavenHome() {
@AfterEach
void clearMavenHome() {
System.clearProperty("maven.home");
}
@@ -55,14 +55,14 @@ public class MavenOperationBlockMacroTests extends AbstractOperationBlockMacroTe
@Override
protected File getBuildOutputLocation() {
File outputLocation = new File(this.temp.getRoot(), "maven-project/target");
File outputLocation = new File(this.temp, "maven-project/target");
outputLocation.mkdirs();
return outputLocation;
}
@Override
protected File getSourceLocation() {
File sourceLocation = new File(this.temp.getRoot(), "maven-project/src/main/asciidoc");
File sourceLocation = new File(this.temp, "maven-project/src/main/asciidoc");
if (!sourceLocation.exists()) {
sourceLocation.mkdirs();
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2024 the original author or authors.
* Copyright 2014-2025 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,9 +21,8 @@ import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
@@ -35,23 +34,23 @@ import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
*/
public class SnippetsDirectoryResolverTests {
@Rule
public TemporaryFolder temporaryFolder = new TemporaryFolder();
@TempDir
File temp;
@Test
public void mavenProjectsUseTargetGeneratedSnippets() throws IOException {
this.temporaryFolder.newFile("pom.xml");
new File(this.temp, "pom.xml").createNewFile();
Map<String, Object> attributes = new HashMap<>();
attributes.put("docdir", new File(this.temporaryFolder.getRoot(), "src/main/asciidoc").getAbsolutePath());
attributes.put("docdir", new File(this.temp, "src/main/asciidoc").getAbsolutePath());
File snippetsDirectory = getMavenSnippetsDirectory(attributes);
assertThat(snippetsDirectory).isAbsolute();
assertThat(snippetsDirectory).isEqualTo(new File(this.temporaryFolder.getRoot(), "target/generated-snippets"));
assertThat(snippetsDirectory).isEqualTo(new File(this.temp, "target/generated-snippets"));
}
@Test
public void illegalStateExceptionWhenMavenPomCannotBeFound() {
Map<String, Object> attributes = new HashMap<>();
String docdir = new File(this.temporaryFolder.getRoot(), "src/main/asciidoc").getAbsolutePath();
String docdir = new File(this.temp, "src/main/asciidoc").getAbsolutePath();
attributes.put("docdir", docdir);
assertThatIllegalStateException().isThrownBy(() -> getMavenSnippetsDirectory(attributes))
.withMessage("pom.xml not found in '" + docdir + "' or above");

View File

@@ -32,6 +32,8 @@ task jmustacheRepackJar(type: Jar) { repackJar ->
}
dependencies {
compileOnly("org.apiguardian:apiguardian-api")
implementation("com.fasterxml.jackson.core:jackson-databind")
implementation("org.springframework:spring-web")
implementation(files(jmustacheRepackJar))
@@ -50,21 +52,29 @@ dependencies {
optional("org.junit.jupiter:junit-jupiter-api")
testFixturesApi(platform(project(":spring-restdocs-platform")))
testFixturesApi("junit:junit")
testFixturesApi("org.assertj:assertj-core")
testFixturesApi("org.hamcrest:hamcrest-core")
testFixturesApi("org.junit.jupiter:junit-jupiter")
testFixturesApi("org.mockito:mockito-core")
testFixturesCompileOnly("org.apiguardian:apiguardian-api")
testFixturesImplementation(files(jmustacheRepackJar))
testFixturesImplementation("org.hamcrest:hamcrest-library")
testFixturesImplementation("org.mockito:mockito-core")
testFixturesImplementation("org.springframework:spring-core")
testFixturesImplementation("org.springframework:spring-web")
testFixturesRuntimeOnly("org.junit.platform:junit-platform-launcher")
testCompileOnly("org.apiguardian:apiguardian-api")
testImplementation("junit:junit")
testImplementation("org.assertj:assertj-core")
testImplementation("org.javamoney:moneta")
testImplementation("org.mockito:mockito-core")
testImplementation("org.springframework:spring-test")
testRuntimeOnly("org.apache.tomcat.embed:tomcat-embed-el")
testRuntimeOnly("org.junit.platform:junit-platform-engine")
}
jar {
@@ -81,3 +91,7 @@ components.java.withVariantsFromConfiguration(configurations.testFixturesApiElem
components.java.withVariantsFromConfiguration(configurations.testFixturesRuntimeElements) {
skip()
}
tasks.named("test") {
useJUnitPlatform()
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2023 the original author or authors.
* Copyright 2014-2025 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.
@@ -74,9 +74,9 @@ public abstract class TemplatedSnippet implements Snippet {
RestDocumentationContext context = (RestDocumentationContext) operation.getAttributes()
.get(RestDocumentationContext.class.getName());
WriterResolver writerResolver = (WriterResolver) operation.getAttributes().get(WriterResolver.class.getName());
Map<String, Object> model = createModel(operation);
model.putAll(this.attributes);
try (Writer writer = writerResolver.resolve(operation.getName(), this.snippetName, context)) {
Map<String, Object> model = createModel(operation);
model.putAll(this.attributes);
TemplateEngine templateEngine = (TemplateEngine) operation.getAttributes()
.get(TemplateEngine.class.getName());
writer.append(templateEngine.compileTemplate(this.templateName).render(model));

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2021 the original author or authors.
* Copyright 2014-2025 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,25 +16,16 @@
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.templates.TemplateFormat;
import org.springframework.restdocs.templates.TemplateFormats;
import org.springframework.restdocs.testfixtures.GeneratedSnippets;
import org.springframework.restdocs.testfixtures.OperationBuilder;
import org.springframework.restdocs.testfixtures.SnippetConditions;
import org.springframework.restdocs.testfixtures.SnippetConditions.CodeBlockCondition;
import org.springframework.restdocs.testfixtures.SnippetConditions.HttpRequestCondition;
import org.springframework.restdocs.testfixtures.SnippetConditions.HttpResponseCondition;
import org.springframework.restdocs.testfixtures.SnippetConditions.TableCondition;
import org.springframework.restdocs.testfixtures.jupiter.AssertableSnippets;
import org.springframework.web.bind.annotation.RequestMethod;
/**
@@ -42,28 +33,11 @@ import org.springframework.web.bind.annotation.RequestMethod;
*
* @author Andy Wilkinson
*/
@RunWith(Parameterized.class)
public abstract class AbstractSnippetTests {
protected final TemplateFormat templateFormat;
protected final TemplateFormat templateFormat = TemplateFormats.asciidoctor();
@Rule
public GeneratedSnippets generatedSnippets;
@Rule
public OperationBuilder operationBuilder;
@Parameters(name = "{0}")
public static List<Object[]> parameters() {
return Arrays.asList(new Object[] { "Asciidoctor", TemplateFormats.asciidoctor() },
new Object[] { "Markdown", TemplateFormats.markdown() });
}
protected AbstractSnippetTests(String name, TemplateFormat templateFormat) {
this.generatedSnippets = new GeneratedSnippets(templateFormat);
this.templateFormat = templateFormat;
this.operationBuilder = new OperationBuilder(this.templateFormat);
}
protected AssertableSnippets snippets;
public CodeBlockCondition<?> codeBlock(String language) {
return this.codeBlock(language, null);

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2023 the original author or authors.
* Copyright 2014-2025 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,7 @@ import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import org.junit.Test;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.InOrder;
import org.mockito.Mockito;
@@ -55,7 +55,7 @@ import static org.mockito.Mockito.verifyNoMoreInteractions;
* @author Andy Wilkinson
* @author Filip Hrisafov
*/
public class RestDocumentationGeneratorTests {
class RestDocumentationGeneratorTests {
@SuppressWarnings("unchecked")
private final RequestConverter<Object> requestConverter = mock(RequestConverter.class);
@@ -80,7 +80,7 @@ public class RestDocumentationGeneratorTests {
private final OperationPreprocessor responsePreprocessor = mock(OperationPreprocessor.class);
@Test
public void basicHandling() throws IOException {
void basicHandling() throws IOException {
given(this.requestConverter.convert(this.request)).willReturn(this.operationRequest);
given(this.responseConverter.convert(this.response)).willReturn(this.operationResponse);
HashMap<String, Object> configuration = new HashMap<>();
@@ -90,7 +90,7 @@ public class RestDocumentationGeneratorTests {
}
@Test
public void defaultSnippetsAreCalled() throws IOException {
void defaultSnippetsAreCalled() throws IOException {
given(this.requestConverter.convert(this.request)).willReturn(this.operationRequest);
given(this.responseConverter.convert(this.response)).willReturn(this.operationResponse);
HashMap<String, Object> configuration = new HashMap<>();
@@ -107,7 +107,7 @@ public class RestDocumentationGeneratorTests {
}
@Test
public void defaultOperationRequestPreprocessorsAreCalled() throws IOException {
void defaultOperationRequestPreprocessorsAreCalled() throws IOException {
given(this.requestConverter.convert(this.request)).willReturn(this.operationRequest);
given(this.responseConverter.convert(this.response)).willReturn(this.operationResponse);
HashMap<String, Object> configuration = new HashMap<>();
@@ -128,7 +128,7 @@ public class RestDocumentationGeneratorTests {
}
@Test
public void defaultOperationResponsePreprocessorsAreCalled() throws IOException {
void defaultOperationResponsePreprocessorsAreCalled() throws IOException {
given(this.requestConverter.convert(this.request)).willReturn(this.operationRequest);
given(this.responseConverter.convert(this.response)).willReturn(this.operationResponse);
HashMap<String, Object> configuration = new HashMap<>();
@@ -149,7 +149,7 @@ public class RestDocumentationGeneratorTests {
}
@Test
public void newGeneratorOnlyCallsItsSnippets() throws IOException {
void newGeneratorOnlyCallsItsSnippets() throws IOException {
OperationRequestPreprocessor requestPreprocessor = mock(OperationRequestPreprocessor.class);
OperationResponsePreprocessor responsePreprocessor = mock(OperationResponsePreprocessor.class);
given(this.requestConverter.convert(this.request)).willReturn(this.operationRequest);

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2024 the original author or authors.
* Copyright 2014-2025 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,7 +19,7 @@ package org.springframework.restdocs.cli;
import java.util.Arrays;
import java.util.Collections;
import org.junit.Test;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
@@ -29,27 +29,27 @@ import static org.assertj.core.api.Assertions.assertThat;
* @author Tomasz Kopczynski
* @author Andy Wilkinson
*/
public class ConcatenatingCommandFormatterTests {
class ConcatenatingCommandFormatterTests {
private CommandFormatter singleLineFormat = new ConcatenatingCommandFormatter(" ");
@Test
public void formattingAnEmptyListProducesAnEmptyString() {
void formattingAnEmptyListProducesAnEmptyString() {
assertThat(this.singleLineFormat.format(Collections.<String>emptyList())).isEqualTo("");
}
@Test
public void formattingNullProducesAnEmptyString() {
void formattingNullProducesAnEmptyString() {
assertThat(this.singleLineFormat.format(null)).isEqualTo("");
}
@Test
public void formattingASingleElement() {
void formattingASingleElement() {
assertThat(this.singleLineFormat.format(Collections.singletonList("alpha"))).isEqualTo(" alpha");
}
@Test
public void formattingMultipleElements() {
void formattingMultipleElements() {
assertThat(this.singleLineFormat.format(Arrays.asList("alpha", "bravo"))).isEqualTo(" alpha bravo");
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2023 the original author or authors.
* Copyright 2014-2025 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,14 +19,11 @@ package org.springframework.restdocs.cli;
import java.io.IOException;
import java.util.Base64;
import org.junit.Test;
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.templates.TemplateFormat;
import org.springframework.restdocs.testfixtures.jupiter.AssertableSnippets;
import org.springframework.restdocs.testfixtures.jupiter.OperationBuilder;
import org.springframework.restdocs.testfixtures.jupiter.RenderedSnippetTest;
import static org.assertj.core.api.Assertions.assertThat;
@@ -40,200 +37,209 @@ import static org.assertj.core.api.Assertions.assertThat;
* @author Paul-Christian Volkmer
* @author Tomasz Kopczynski
*/
@RunWith(Parameterized.class)
public class CurlRequestSnippetTests extends AbstractSnippetTests {
class CurlRequestSnippetTests {
private CommandFormatter commandFormatter = CliDocumentation.singleLineFormat();
public CurlRequestSnippetTests(String name, TemplateFormat templateFormat) {
super(name, templateFormat);
}
@Test
public void getRequest() throws IOException {
@RenderedSnippetTest
void getRequest(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
new CurlRequestSnippet(this.commandFormatter)
.document(this.operationBuilder.request("http://localhost/foo").build());
assertThat(this.generatedSnippets.curlRequest())
.is(codeBlock("bash").withContent("$ curl 'http://localhost/foo' -i -X GET"));
.document(operationBuilder.request("http://localhost/foo").build());
assertThat(snippets.curlRequest()).isCodeBlock(
(codeBlock) -> codeBlock.withLanguage("bash").content("$ curl 'http://localhost/foo' -i -X GET"));
}
@Test
public void nonGetRequest() throws IOException {
@RenderedSnippetTest
void nonGetRequest(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
new CurlRequestSnippet(this.commandFormatter)
.document(this.operationBuilder.request("http://localhost/foo").method("POST").build());
assertThat(this.generatedSnippets.curlRequest())
.is(codeBlock("bash").withContent("$ curl 'http://localhost/foo' -i -X POST"));
.document(operationBuilder.request("http://localhost/foo").method("POST").build());
assertThat(snippets.curlRequest()).isCodeBlock(
(codeBlock) -> codeBlock.withLanguage("bash").content("$ curl 'http://localhost/foo' -i -X POST"));
}
@Test
public void requestWithContent() throws IOException {
@RenderedSnippetTest
void requestWithContent(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
new CurlRequestSnippet(this.commandFormatter)
.document(this.operationBuilder.request("http://localhost/foo").content("content").build());
assertThat(this.generatedSnippets.curlRequest())
.is(codeBlock("bash").withContent("$ curl 'http://localhost/foo' -i -X GET -d 'content'"));
.document(operationBuilder.request("http://localhost/foo").content("content").build());
assertThat(snippets.curlRequest()).isCodeBlock((codeBlock) -> codeBlock.withLanguage("bash")
.content("$ curl 'http://localhost/foo' -i -X GET -d 'content'"));
}
@Test
public void getRequestWithQueryString() throws IOException {
@RenderedSnippetTest
void getRequestWithQueryString(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
new CurlRequestSnippet(this.commandFormatter)
.document(this.operationBuilder.request("http://localhost/foo?param=value").build());
assertThat(this.generatedSnippets.curlRequest())
.is(codeBlock("bash").withContent("$ curl 'http://localhost/foo?param=value' -i -X GET"));
.document(operationBuilder.request("http://localhost/foo?param=value").build());
assertThat(snippets.curlRequest()).isCodeBlock((codeBlock) -> codeBlock.withLanguage("bash")
.content("$ curl 'http://localhost/foo?param=value' -i -X GET"));
}
@Test
public void getRequestWithQueryStringWithNoValue() throws IOException {
@RenderedSnippetTest
void getRequestWithQueryStringWithNoValue(OperationBuilder operationBuilder, AssertableSnippets snippets)
throws IOException {
new CurlRequestSnippet(this.commandFormatter)
.document(this.operationBuilder.request("http://localhost/foo?param").build());
assertThat(this.generatedSnippets.curlRequest())
.is(codeBlock("bash").withContent("$ curl 'http://localhost/foo?param' -i -X GET"));
.document(operationBuilder.request("http://localhost/foo?param").build());
assertThat(snippets.curlRequest()).isCodeBlock(
(codeBlock) -> codeBlock.withLanguage("bash").content("$ curl 'http://localhost/foo?param' -i -X GET"));
}
@Test
public void postRequestWithQueryString() throws IOException {
@RenderedSnippetTest
void postRequestWithQueryString(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
new CurlRequestSnippet(this.commandFormatter)
.document(this.operationBuilder.request("http://localhost/foo?param=value").method("POST").build());
assertThat(this.generatedSnippets.curlRequest())
.is(codeBlock("bash").withContent("$ curl 'http://localhost/foo?param=value' -i -X POST"));
.document(operationBuilder.request("http://localhost/foo?param=value").method("POST").build());
assertThat(snippets.curlRequest()).isCodeBlock((codeBlock) -> codeBlock.withLanguage("bash")
.content("$ curl 'http://localhost/foo?param=value' -i -X POST"));
}
@Test
public void postRequestWithQueryStringWithNoValue() throws IOException {
@RenderedSnippetTest
void postRequestWithQueryStringWithNoValue(OperationBuilder operationBuilder, AssertableSnippets snippets)
throws IOException {
new CurlRequestSnippet(this.commandFormatter)
.document(this.operationBuilder.request("http://localhost/foo?param").method("POST").build());
assertThat(this.generatedSnippets.curlRequest())
.is(codeBlock("bash").withContent("$ curl 'http://localhost/foo?param' -i -X POST"));
.document(operationBuilder.request("http://localhost/foo?param").method("POST").build());
assertThat(snippets.curlRequest()).isCodeBlock((codeBlock) -> codeBlock.withLanguage("bash")
.content("$ curl 'http://localhost/foo?param' -i -X POST"));
}
@Test
public void postRequestWithOneParameter() throws IOException {
@RenderedSnippetTest
void postRequestWithOneParameter(OperationBuilder operationBuilder, AssertableSnippets snippets)
throws IOException {
new CurlRequestSnippet(this.commandFormatter)
.document(this.operationBuilder.request("http://localhost/foo").method("POST").content("k1=v1").build());
assertThat(this.generatedSnippets.curlRequest())
.is(codeBlock("bash").withContent("$ curl 'http://localhost/foo' -i -X POST -d 'k1=v1'"));
.document(operationBuilder.request("http://localhost/foo").method("POST").content("k1=v1").build());
assertThat(snippets.curlRequest()).isCodeBlock((codeBlock) -> codeBlock.withLanguage("bash")
.content("$ curl 'http://localhost/foo' -i -X POST -d 'k1=v1'"));
}
@Test
public void postRequestWithOneParameterAndExplicitContentType() throws IOException {
new CurlRequestSnippet(this.commandFormatter).document(this.operationBuilder.request("http://localhost/foo")
@RenderedSnippetTest
void postRequestWithOneParameterAndExplicitContentType(OperationBuilder operationBuilder,
AssertableSnippets snippets) throws IOException {
new CurlRequestSnippet(this.commandFormatter).document(operationBuilder.request("http://localhost/foo")
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED_VALUE)
.method("POST")
.content("k1=v1")
.build());
assertThat(this.generatedSnippets.curlRequest())
.is(codeBlock("bash").withContent("$ curl 'http://localhost/foo' -i -X POST -d 'k1=v1'"));
assertThat(snippets.curlRequest()).isCodeBlock((codeBlock) -> codeBlock.withLanguage("bash")
.content("$ curl 'http://localhost/foo' -i -X POST -d 'k1=v1'"));
}
@Test
public void postRequestWithOneParameterWithNoValue() throws IOException {
@RenderedSnippetTest
void postRequestWithOneParameterWithNoValue(OperationBuilder operationBuilder, AssertableSnippets snippets)
throws IOException {
new CurlRequestSnippet(this.commandFormatter)
.document(this.operationBuilder.request("http://localhost/foo").method("POST").content("k1=").build());
assertThat(this.generatedSnippets.curlRequest())
.is(codeBlock("bash").withContent("$ curl 'http://localhost/foo' -i -X POST -d 'k1='"));
.document(operationBuilder.request("http://localhost/foo").method("POST").content("k1=").build());
assertThat(snippets.curlRequest()).isCodeBlock((codeBlock) -> codeBlock.withLanguage("bash")
.content("$ curl 'http://localhost/foo' -i -X POST -d 'k1='"));
}
@Test
public void postRequestWithMultipleParameters() throws IOException {
new CurlRequestSnippet(this.commandFormatter).document(this.operationBuilder.request("http://localhost/foo")
@RenderedSnippetTest
void postRequestWithMultipleParameters(OperationBuilder operationBuilder, AssertableSnippets snippets)
throws IOException {
new CurlRequestSnippet(this.commandFormatter).document(operationBuilder.request("http://localhost/foo")
.method("POST")
.content("k1=v1&k1=v1-bis&k2=v2")
.build());
assertThat(this.generatedSnippets.curlRequest()).is(codeBlock("bash")
.withContent("$ curl 'http://localhost/foo' -i -X POST" + " -d 'k1=v1&k1=v1-bis&k2=v2'"));
assertThat(snippets.curlRequest()).isCodeBlock((codeBlock) -> codeBlock.withLanguage("bash")
.content("$ curl 'http://localhost/foo' -i -X POST" + " -d 'k1=v1&k1=v1-bis&k2=v2'"));
}
@Test
public void postRequestWithUrlEncodedParameter() throws IOException {
@RenderedSnippetTest
void postRequestWithUrlEncodedParameter(OperationBuilder operationBuilder, AssertableSnippets snippets)
throws IOException {
new CurlRequestSnippet(this.commandFormatter)
.document(this.operationBuilder.request("http://localhost/foo").method("POST").content("k1=a%26b").build());
assertThat(this.generatedSnippets.curlRequest())
.is(codeBlock("bash").withContent("$ curl 'http://localhost/foo' -i -X POST -d 'k1=a%26b'"));
.document(operationBuilder.request("http://localhost/foo").method("POST").content("k1=a%26b").build());
assertThat(snippets.curlRequest()).isCodeBlock((codeBlock) -> codeBlock.withLanguage("bash")
.content("$ curl 'http://localhost/foo' -i -X POST -d 'k1=a%26b'"));
}
@Test
public void postRequestWithJsonData() throws IOException {
new CurlRequestSnippet(this.commandFormatter).document(this.operationBuilder.request("http://localhost/foo")
@RenderedSnippetTest
void postRequestWithJsonData(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
new CurlRequestSnippet(this.commandFormatter).document(operationBuilder.request("http://localhost/foo")
.method("POST")
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
.content("{\"a\":\"alpha\"}")
.build());
assertThat(this.generatedSnippets.curlRequest()).is(codeBlock("bash").withContent(
"$ curl 'http://localhost/foo' -i -X POST -H 'Content-Type: application/json' -d '{\"a\":\"alpha\"}'"));
assertThat(snippets.curlRequest()).isCodeBlock((codeBlock) -> codeBlock.withLanguage("bash")
.content(
"$ curl 'http://localhost/foo' -i -X POST -H 'Content-Type: application/json' -d '{\"a\":\"alpha\"}'"));
}
@Test
public void putRequestWithOneParameter() throws IOException {
@RenderedSnippetTest
void putRequestWithOneParameter(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
new CurlRequestSnippet(this.commandFormatter)
.document(this.operationBuilder.request("http://localhost/foo").method("PUT").content("k1=v1").build());
assertThat(this.generatedSnippets.curlRequest())
.is(codeBlock("bash").withContent("$ curl 'http://localhost/foo' -i -X PUT -d 'k1=v1'"));
.document(operationBuilder.request("http://localhost/foo").method("PUT").content("k1=v1").build());
assertThat(snippets.curlRequest()).isCodeBlock((codeBlock) -> codeBlock.withLanguage("bash")
.content("$ curl 'http://localhost/foo' -i -X PUT -d 'k1=v1'"));
}
@Test
public void putRequestWithMultipleParameters() throws IOException {
new CurlRequestSnippet(this.commandFormatter).document(this.operationBuilder.request("http://localhost/foo")
@RenderedSnippetTest
void putRequestWithMultipleParameters(OperationBuilder operationBuilder, AssertableSnippets snippets)
throws IOException {
new CurlRequestSnippet(this.commandFormatter).document(operationBuilder.request("http://localhost/foo")
.method("PUT")
.content("k1=v1&k1=v1-bis&k2=v2")
.build());
assertThat(this.generatedSnippets.curlRequest()).is(codeBlock("bash")
.withContent("$ curl 'http://localhost/foo' -i -X PUT" + " -d 'k1=v1&k1=v1-bis&k2=v2'"));
assertThat(snippets.curlRequest()).isCodeBlock((codeBlock) -> codeBlock.withLanguage("bash")
.content("$ curl 'http://localhost/foo' -i -X PUT" + " -d 'k1=v1&k1=v1-bis&k2=v2'"));
}
@Test
public void putRequestWithUrlEncodedParameter() throws IOException {
@RenderedSnippetTest
void putRequestWithUrlEncodedParameter(OperationBuilder operationBuilder, AssertableSnippets snippets)
throws IOException {
new CurlRequestSnippet(this.commandFormatter)
.document(this.operationBuilder.request("http://localhost/foo").method("PUT").content("k1=a%26b").build());
assertThat(this.generatedSnippets.curlRequest())
.is(codeBlock("bash").withContent("$ curl 'http://localhost/foo' -i -X PUT -d 'k1=a%26b'"));
.document(operationBuilder.request("http://localhost/foo").method("PUT").content("k1=a%26b").build());
assertThat(snippets.curlRequest()).isCodeBlock((codeBlock) -> codeBlock.withLanguage("bash")
.content("$ curl 'http://localhost/foo' -i -X PUT -d 'k1=a%26b'"));
}
@Test
public void requestWithHeaders() throws IOException {
new CurlRequestSnippet(this.commandFormatter).document(this.operationBuilder.request("http://localhost/foo")
@RenderedSnippetTest
void requestWithHeaders(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
new CurlRequestSnippet(this.commandFormatter).document(operationBuilder.request("http://localhost/foo")
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
.header("a", "alpha")
.build());
assertThat(this.generatedSnippets.curlRequest()).is(codeBlock("bash").withContent(
"$ curl 'http://localhost/foo' -i -X GET" + " -H 'Content-Type: application/json' -H 'a: alpha'"));
assertThat(snippets.curlRequest()).isCodeBlock((codeBlock) -> codeBlock.withLanguage("bash")
.content("$ curl 'http://localhost/foo' -i -X GET" + " -H 'Content-Type: application/json' -H 'a: alpha'"));
}
@Test
public void requestWithHeadersMultiline() throws IOException {
@RenderedSnippetTest
void requestWithHeadersMultiline(OperationBuilder operationBuilder, AssertableSnippets snippets)
throws IOException {
new CurlRequestSnippet(CliDocumentation.multiLineFormat())
.document(this.operationBuilder.request("http://localhost/foo")
.document(operationBuilder.request("http://localhost/foo")
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
.header("a", "alpha")
.build());
assertThat(this.generatedSnippets.curlRequest())
.is(codeBlock("bash").withContent(String.format("$ curl 'http://localhost/foo' -i -X GET \\%n"
assertThat(snippets.curlRequest()).isCodeBlock((codeBlock) -> codeBlock.withLanguage("bash")
.content(String.format("$ curl 'http://localhost/foo' -i -X GET \\%n"
+ " -H 'Content-Type: application/json' \\%n" + " -H 'a: alpha'")));
}
@Test
public void requestWithCookies() throws IOException {
new CurlRequestSnippet(this.commandFormatter).document(this.operationBuilder.request("http://localhost/foo")
@RenderedSnippetTest
void requestWithCookies(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
new CurlRequestSnippet(this.commandFormatter).document(operationBuilder.request("http://localhost/foo")
.cookie("name1", "value1")
.cookie("name2", "value2")
.build());
assertThat(this.generatedSnippets.curlRequest()).is(codeBlock("bash")
.withContent("$ curl 'http://localhost/foo' -i -X GET" + " --cookie 'name1=value1;name2=value2'"));
assertThat(snippets.curlRequest()).isCodeBlock((codeBlock) -> codeBlock.withLanguage("bash")
.content("$ curl 'http://localhost/foo' -i -X GET" + " --cookie 'name1=value1;name2=value2'"));
}
@Test
public void multipartPostWithNoSubmittedFileName() throws IOException {
new CurlRequestSnippet(this.commandFormatter).document(this.operationBuilder.request("http://localhost/upload")
@RenderedSnippetTest
void multipartPostWithNoSubmittedFileName(OperationBuilder operationBuilder, AssertableSnippets snippets)
throws IOException {
new CurlRequestSnippet(this.commandFormatter).document(operationBuilder.request("http://localhost/upload")
.method("POST")
.header(HttpHeaders.CONTENT_TYPE, MediaType.MULTIPART_FORM_DATA_VALUE)
.part("metadata", "{\"description\": \"foo\"}".getBytes())
.build());
String expectedContent = "$ curl 'http://localhost/upload' -i -X POST -H "
+ "'Content-Type: multipart/form-data' -F " + "'metadata={\"description\": \"foo\"}'";
assertThat(this.generatedSnippets.curlRequest()).is(codeBlock("bash").withContent(expectedContent));
assertThat(snippets.curlRequest())
.isCodeBlock((codeBlock) -> codeBlock.withLanguage("bash").content(expectedContent));
}
@Test
public void multipartPostWithContentType() throws IOException {
new CurlRequestSnippet(this.commandFormatter).document(this.operationBuilder.request("http://localhost/upload")
@RenderedSnippetTest
void multipartPostWithContentType(OperationBuilder operationBuilder, AssertableSnippets snippets)
throws IOException {
new CurlRequestSnippet(this.commandFormatter).document(operationBuilder.request("http://localhost/upload")
.method("POST")
.header(HttpHeaders.CONTENT_TYPE, MediaType.MULTIPART_FORM_DATA_VALUE)
.part("image", new byte[0])
@@ -242,12 +248,13 @@ public class CurlRequestSnippetTests extends AbstractSnippetTests {
.build());
String expectedContent = "$ curl 'http://localhost/upload' -i -X POST -H "
+ "'Content-Type: multipart/form-data' -F " + "'image=@documents/images/example.png;type=image/png'";
assertThat(this.generatedSnippets.curlRequest()).is(codeBlock("bash").withContent(expectedContent));
assertThat(snippets.curlRequest())
.isCodeBlock((codeBlock) -> codeBlock.withLanguage("bash").content(expectedContent));
}
@Test
public void multipartPost() throws IOException {
new CurlRequestSnippet(this.commandFormatter).document(this.operationBuilder.request("http://localhost/upload")
@RenderedSnippetTest
void multipartPost(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
new CurlRequestSnippet(this.commandFormatter).document(operationBuilder.request("http://localhost/upload")
.method("POST")
.header(HttpHeaders.CONTENT_TYPE, MediaType.MULTIPART_FORM_DATA_VALUE)
.part("image", new byte[0])
@@ -255,36 +262,38 @@ public class CurlRequestSnippetTests extends AbstractSnippetTests {
.build());
String expectedContent = "$ curl 'http://localhost/upload' -i -X POST -H "
+ "'Content-Type: multipart/form-data' -F " + "'image=@documents/images/example.png'";
assertThat(this.generatedSnippets.curlRequest()).is(codeBlock("bash").withContent(expectedContent));
assertThat(snippets.curlRequest())
.isCodeBlock((codeBlock) -> codeBlock.withLanguage("bash").content(expectedContent));
}
@Test
public void basicAuthCredentialsAreSuppliedUsingUserOption() throws IOException {
new CurlRequestSnippet(this.commandFormatter).document(this.operationBuilder.request("http://localhost/foo")
@RenderedSnippetTest
void basicAuthCredentialsAreSuppliedUsingUserOption(OperationBuilder operationBuilder, AssertableSnippets snippets)
throws IOException {
new CurlRequestSnippet(this.commandFormatter).document(operationBuilder.request("http://localhost/foo")
.header(HttpHeaders.AUTHORIZATION, "Basic " + Base64.getEncoder().encodeToString("user:secret".getBytes()))
.build());
assertThat(this.generatedSnippets.curlRequest())
.is(codeBlock("bash").withContent("$ curl 'http://localhost/foo' -i -u 'user:secret' -X GET"));
assertThat(snippets.curlRequest()).isCodeBlock((codeBlock) -> codeBlock.withLanguage("bash")
.content("$ curl 'http://localhost/foo' -i -u 'user:secret' -X GET"));
}
@Test
public void customAttributes() throws IOException {
new CurlRequestSnippet(this.commandFormatter).document(this.operationBuilder.request("http://localhost/foo")
@RenderedSnippetTest
void customAttributes(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
new CurlRequestSnippet(this.commandFormatter).document(operationBuilder.request("http://localhost/foo")
.header(HttpHeaders.HOST, "api.example.com")
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
.header("a", "alpha")
.build());
assertThat(this.generatedSnippets.curlRequest())
.is(codeBlock("bash").withContent("$ curl 'http://localhost/foo' -i -X GET -H 'Host: api.example.com'"
assertThat(snippets.curlRequest()).isCodeBlock((codeBlock) -> codeBlock.withLanguage("bash")
.content("$ curl 'http://localhost/foo' -i -X GET -H 'Host: api.example.com'"
+ " -H 'Content-Type: application/json' -H 'a: alpha'"));
}
@Test
public void deleteWithQueryString() throws IOException {
@RenderedSnippetTest
void deleteWithQueryString(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
new CurlRequestSnippet(this.commandFormatter)
.document(this.operationBuilder.request("http://localhost/foo?a=alpha&b=bravo").method("DELETE").build());
assertThat(this.generatedSnippets.curlRequest())
.is(codeBlock("bash").withContent("$ curl 'http://localhost/foo?a=alpha&b=bravo' -i " + "-X DELETE"));
.document(operationBuilder.request("http://localhost/foo?a=alpha&b=bravo").method("DELETE").build());
assertThat(snippets.curlRequest()).isCodeBlock((codeBlock) -> codeBlock.withLanguage("bash")
.content("$ curl 'http://localhost/foo?a=alpha&b=bravo' -i " + "-X DELETE"));
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2023 the original author or authors.
* Copyright 2014-2025 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,14 +19,11 @@ package org.springframework.restdocs.cli;
import java.io.IOException;
import java.util.Base64;
import org.junit.Test;
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.templates.TemplateFormat;
import org.springframework.restdocs.testfixtures.jupiter.AssertableSnippets;
import org.springframework.restdocs.testfixtures.jupiter.OperationBuilder;
import org.springframework.restdocs.testfixtures.jupiter.RenderedSnippetTest;
import static org.assertj.core.api.Assertions.assertThat;
@@ -41,249 +38,257 @@ import static org.assertj.core.api.Assertions.assertThat;
* @author Raman Gupta
* @author Tomasz Kopczynski
*/
@RunWith(Parameterized.class)
public class HttpieRequestSnippetTests extends AbstractSnippetTests {
class HttpieRequestSnippetTests {
private CommandFormatter commandFormatter = CliDocumentation.singleLineFormat();
public HttpieRequestSnippetTests(String name, TemplateFormat templateFormat) {
super(name, templateFormat);
}
@Test
public void getRequest() throws IOException {
@RenderedSnippetTest
void getRequest(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
new HttpieRequestSnippet(this.commandFormatter)
.document(this.operationBuilder.request("http://localhost/foo").build());
assertThat(this.generatedSnippets.httpieRequest())
.is(codeBlock("bash").withContent("$ http GET 'http://localhost/foo'"));
.document(operationBuilder.request("http://localhost/foo").build());
assertThat(snippets.httpieRequest())
.isCodeBlock((codeBlock) -> codeBlock.withLanguage("bash").content("$ http GET 'http://localhost/foo'"));
}
@Test
public void nonGetRequest() throws IOException {
@RenderedSnippetTest
void nonGetRequest(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
new HttpieRequestSnippet(this.commandFormatter)
.document(this.operationBuilder.request("http://localhost/foo").method("POST").build());
assertThat(this.generatedSnippets.httpieRequest())
.is(codeBlock("bash").withContent("$ http POST 'http://localhost/foo'"));
.document(operationBuilder.request("http://localhost/foo").method("POST").build());
assertThat(snippets.httpieRequest())
.isCodeBlock((codeBlock) -> codeBlock.withLanguage("bash").content("$ http POST 'http://localhost/foo'"));
}
@Test
public void requestWithContent() throws IOException {
@RenderedSnippetTest
void requestWithContent(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
new HttpieRequestSnippet(this.commandFormatter)
.document(this.operationBuilder.request("http://localhost/foo").content("content").build());
assertThat(this.generatedSnippets.httpieRequest())
.is(codeBlock("bash").withContent("$ echo 'content' | http GET 'http://localhost/foo'"));
.document(operationBuilder.request("http://localhost/foo").content("content").build());
assertThat(snippets.httpieRequest()).isCodeBlock((codeBlock) -> codeBlock.withLanguage("bash")
.content("$ echo 'content' | http GET 'http://localhost/foo'"));
}
@Test
public void getRequestWithQueryString() throws IOException {
@RenderedSnippetTest
void getRequestWithQueryString(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
new HttpieRequestSnippet(this.commandFormatter)
.document(this.operationBuilder.request("http://localhost/foo?param=value").build());
assertThat(this.generatedSnippets.httpieRequest())
.is(codeBlock("bash").withContent("$ http GET 'http://localhost/foo?param=value'"));
.document(operationBuilder.request("http://localhost/foo?param=value").build());
assertThat(snippets.httpieRequest()).isCodeBlock(
(codeBlock) -> codeBlock.withLanguage("bash").content("$ http GET 'http://localhost/foo?param=value'"));
}
@Test
public void getRequestWithQueryStringWithNoValue() throws IOException {
@RenderedSnippetTest
void getRequestWithQueryStringWithNoValue(OperationBuilder operationBuilder, AssertableSnippets snippets)
throws IOException {
new HttpieRequestSnippet(this.commandFormatter)
.document(this.operationBuilder.request("http://localhost/foo?param").build());
assertThat(this.generatedSnippets.httpieRequest())
.is(codeBlock("bash").withContent("$ http GET 'http://localhost/foo?param'"));
.document(operationBuilder.request("http://localhost/foo?param").build());
assertThat(snippets.httpieRequest()).isCodeBlock(
(codeBlock) -> codeBlock.withLanguage("bash").content("$ http GET 'http://localhost/foo?param'"));
}
@Test
public void postRequestWithQueryString() throws IOException {
@RenderedSnippetTest
void postRequestWithQueryString(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
new HttpieRequestSnippet(this.commandFormatter)
.document(this.operationBuilder.request("http://localhost/foo?param=value").method("POST").build());
assertThat(this.generatedSnippets.httpieRequest())
.is(codeBlock("bash").withContent("$ http POST 'http://localhost/foo?param=value'"));
.document(operationBuilder.request("http://localhost/foo?param=value").method("POST").build());
assertThat(snippets.httpieRequest()).isCodeBlock((codeBlock) -> codeBlock.withLanguage("bash")
.content("$ http POST 'http://localhost/foo?param=value'"));
}
@Test
public void postRequestWithQueryStringWithNoValue() throws IOException {
@RenderedSnippetTest
void postRequestWithQueryStringWithNoValue(OperationBuilder operationBuilder, AssertableSnippets snippets)
throws IOException {
new HttpieRequestSnippet(this.commandFormatter)
.document(this.operationBuilder.request("http://localhost/foo?param").method("POST").build());
assertThat(this.generatedSnippets.httpieRequest())
.is(codeBlock("bash").withContent("$ http POST 'http://localhost/foo?param'"));
.document(operationBuilder.request("http://localhost/foo?param").method("POST").build());
assertThat(snippets.httpieRequest()).isCodeBlock(
(codeBlock) -> codeBlock.withLanguage("bash").content("$ http POST 'http://localhost/foo?param'"));
}
@Test
public void postRequestWithOneParameter() throws IOException {
new HttpieRequestSnippet(this.commandFormatter).document(this.operationBuilder.request("http://localhost/foo")
@RenderedSnippetTest
void postRequestWithOneParameter(OperationBuilder operationBuilder, AssertableSnippets snippets)
throws IOException {
new HttpieRequestSnippet(this.commandFormatter).document(operationBuilder.request("http://localhost/foo")
.method("POST")
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED_VALUE)
.content("k1=v1")
.build());
assertThat(this.generatedSnippets.httpieRequest())
.is(codeBlock("bash").withContent("$ http --form POST 'http://localhost/foo' 'k1=v1'"));
assertThat(snippets.httpieRequest()).isCodeBlock((codeBlock) -> codeBlock.withLanguage("bash")
.content("$ http --form POST 'http://localhost/foo' 'k1=v1'"));
}
@Test
public void postRequestWithOneParameterWithNoValue() throws IOException {
new HttpieRequestSnippet(this.commandFormatter).document(this.operationBuilder.request("http://localhost/foo")
@RenderedSnippetTest
void postRequestWithOneParameterWithNoValue(OperationBuilder operationBuilder, AssertableSnippets snippets)
throws IOException {
new HttpieRequestSnippet(this.commandFormatter).document(operationBuilder.request("http://localhost/foo")
.method("POST")
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED_VALUE)
.content("k1")
.build());
assertThat(this.generatedSnippets.httpieRequest())
.is(codeBlock("bash").withContent("$ http --form POST 'http://localhost/foo' 'k1='"));
assertThat(snippets.httpieRequest()).isCodeBlock((codeBlock) -> codeBlock.withLanguage("bash")
.content("$ http --form POST 'http://localhost/foo' 'k1='"));
}
@Test
public void postRequestWithMultipleParameters() throws IOException {
new HttpieRequestSnippet(this.commandFormatter).document(this.operationBuilder.request("http://localhost/foo")
@RenderedSnippetTest
void postRequestWithMultipleParameters(OperationBuilder operationBuilder, AssertableSnippets snippets)
throws IOException {
new HttpieRequestSnippet(this.commandFormatter).document(operationBuilder.request("http://localhost/foo")
.method("POST")
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED_VALUE)
.content("k1=v1&k1=v1-bis&k2=v2")
.build());
assertThat(this.generatedSnippets.httpieRequest())
.is(codeBlock("bash").withContent("$ http --form POST 'http://localhost/foo' 'k1=v1' 'k1=v1-bis' 'k2=v2'"));
assertThat(snippets.httpieRequest()).isCodeBlock((codeBlock) -> codeBlock.withLanguage("bash")
.content("$ http --form POST 'http://localhost/foo' 'k1=v1' 'k1=v1-bis' 'k2=v2'"));
}
@Test
public void postRequestWithUrlEncodedParameter() throws IOException {
new HttpieRequestSnippet(this.commandFormatter).document(this.operationBuilder.request("http://localhost/foo")
@RenderedSnippetTest
void postRequestWithUrlEncodedParameter(OperationBuilder operationBuilder, AssertableSnippets snippets)
throws IOException {
new HttpieRequestSnippet(this.commandFormatter).document(operationBuilder.request("http://localhost/foo")
.method("POST")
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED_VALUE)
.content("k1=a%26b")
.build());
assertThat(this.generatedSnippets.httpieRequest())
.is(codeBlock("bash").withContent("$ http --form POST 'http://localhost/foo' 'k1=a&b'"));
assertThat(snippets.httpieRequest()).isCodeBlock((codeBlock) -> codeBlock.withLanguage("bash")
.content("$ http --form POST 'http://localhost/foo' 'k1=a&b'"));
}
@Test
public void putRequestWithOneParameter() throws IOException {
new HttpieRequestSnippet(this.commandFormatter).document(this.operationBuilder.request("http://localhost/foo")
@RenderedSnippetTest
void putRequestWithOneParameter(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
new HttpieRequestSnippet(this.commandFormatter).document(operationBuilder.request("http://localhost/foo")
.method("PUT")
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED_VALUE)
.content("k1=v1")
.build());
assertThat(this.generatedSnippets.httpieRequest())
.is(codeBlock("bash").withContent("$ http --form PUT 'http://localhost/foo' 'k1=v1'"));
assertThat(snippets.httpieRequest()).isCodeBlock((codeBlock) -> codeBlock.withLanguage("bash")
.content("$ http --form PUT 'http://localhost/foo' 'k1=v1'"));
}
@Test
public void putRequestWithMultipleParameters() throws IOException {
new HttpieRequestSnippet(this.commandFormatter).document(this.operationBuilder.request("http://localhost/foo")
@RenderedSnippetTest
void putRequestWithMultipleParameters(OperationBuilder operationBuilder, AssertableSnippets snippets)
throws IOException {
new HttpieRequestSnippet(this.commandFormatter).document(operationBuilder.request("http://localhost/foo")
.method("PUT")
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED_VALUE)
.content("k1=v1&k1=v1-bis&k2=v2")
.build());
assertThat(this.generatedSnippets.httpieRequest()).is(codeBlock("bash")
.withContent("$ http --form PUT 'http://localhost/foo'" + " 'k1=v1' 'k1=v1-bis' 'k2=v2'"));
assertThat(snippets.httpieRequest()).isCodeBlock((codeBlock) -> codeBlock.withLanguage("bash")
.content("$ http --form PUT 'http://localhost/foo'" + " 'k1=v1' 'k1=v1-bis' 'k2=v2'"));
}
@Test
public void putRequestWithUrlEncodedParameter() throws IOException {
new HttpieRequestSnippet(this.commandFormatter).document(this.operationBuilder.request("http://localhost/foo")
@RenderedSnippetTest
void putRequestWithUrlEncodedParameter(OperationBuilder operationBuilder, AssertableSnippets snippets)
throws IOException {
new HttpieRequestSnippet(this.commandFormatter).document(operationBuilder.request("http://localhost/foo")
.method("PUT")
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED_VALUE)
.content("k1=a%26b")
.build());
assertThat(this.generatedSnippets.httpieRequest())
.is(codeBlock("bash").withContent("$ http --form PUT 'http://localhost/foo' 'k1=a&b'"));
assertThat(snippets.httpieRequest()).isCodeBlock((codeBlock) -> codeBlock.withLanguage("bash")
.content("$ http --form PUT 'http://localhost/foo' 'k1=a&b'"));
}
@Test
public void requestWithHeaders() throws IOException {
new HttpieRequestSnippet(this.commandFormatter).document(this.operationBuilder.request("http://localhost/foo")
@RenderedSnippetTest
void requestWithHeaders(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
new HttpieRequestSnippet(this.commandFormatter).document(operationBuilder.request("http://localhost/foo")
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
.header("a", "alpha")
.build());
assertThat(this.generatedSnippets.httpieRequest()).is(codeBlock("bash")
.withContent("$ http GET 'http://localhost/foo'" + " 'Content-Type:application/json' 'a:alpha'"));
assertThat(snippets.httpieRequest()).isCodeBlock((codeBlock) -> codeBlock.withLanguage("bash")
.content(("$ http GET 'http://localhost/foo'" + " 'Content-Type:application/json' 'a:alpha'")));
}
@Test
public void requestWithHeadersMultiline() throws IOException {
@RenderedSnippetTest
void requestWithHeadersMultiline(OperationBuilder operationBuilder, AssertableSnippets snippets)
throws IOException {
new HttpieRequestSnippet(CliDocumentation.multiLineFormat())
.document(this.operationBuilder.request("http://localhost/foo")
.document(operationBuilder.request("http://localhost/foo")
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
.header("a", "alpha")
.build());
assertThat(this.generatedSnippets.httpieRequest()).is(codeBlock("bash").withContent(String.format(
"$ http GET 'http://localhost/foo' \\%n" + " 'Content-Type:application/json' \\%n 'a:alpha'")));
assertThat(snippets.httpieRequest()).isCodeBlock((codeBlock) -> codeBlock.withLanguage("bash")
.content(String.format("$ http GET 'http://localhost/foo' \\%n"
+ " 'Content-Type:application/json' \\%n 'a:alpha'")));
}
@Test
public void requestWithCookies() throws IOException {
new HttpieRequestSnippet(this.commandFormatter).document(this.operationBuilder.request("http://localhost/foo")
@RenderedSnippetTest
void requestWithCookies(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
new HttpieRequestSnippet(this.commandFormatter).document(operationBuilder.request("http://localhost/foo")
.cookie("name1", "value1")
.cookie("name2", "value2")
.build());
assertThat(this.generatedSnippets.httpieRequest()).is(codeBlock("bash")
.withContent("$ http GET 'http://localhost/foo'" + " 'Cookie:name1=value1' 'Cookie:name2=value2'"));
assertThat(snippets.httpieRequest()).isCodeBlock((codeBlock) -> codeBlock.withLanguage("bash")
.content(("$ http GET 'http://localhost/foo'" + " 'Cookie:name1=value1' 'Cookie:name2=value2'")));
}
@Test
public void multipartPostWithNoSubmittedFileName() throws IOException {
new HttpieRequestSnippet(this.commandFormatter)
.document(this.operationBuilder.request("http://localhost/upload")
.method("POST")
.header(HttpHeaders.CONTENT_TYPE, MediaType.MULTIPART_FORM_DATA_VALUE)
.part("metadata", "{\"description\": \"foo\"}".getBytes())
.build());
@RenderedSnippetTest
void multipartPostWithNoSubmittedFileName(OperationBuilder operationBuilder, AssertableSnippets snippets)
throws IOException {
new HttpieRequestSnippet(this.commandFormatter).document(operationBuilder.request("http://localhost/upload")
.method("POST")
.header(HttpHeaders.CONTENT_TYPE, MediaType.MULTIPART_FORM_DATA_VALUE)
.part("metadata", "{\"description\": \"foo\"}".getBytes())
.build());
String expectedContent = "$ http --multipart POST 'http://localhost/upload'"
+ " 'metadata'='{\"description\": \"foo\"}'";
assertThat(this.generatedSnippets.httpieRequest()).is(codeBlock("bash").withContent(expectedContent));
assertThat(snippets.httpieRequest())
.isCodeBlock((codeBlock) -> codeBlock.withLanguage("bash").content(expectedContent));
}
@Test
public void multipartPostWithContentType() throws IOException {
new HttpieRequestSnippet(this.commandFormatter)
.document(this.operationBuilder.request("http://localhost/upload")
.method("POST")
.header(HttpHeaders.CONTENT_TYPE, MediaType.MULTIPART_FORM_DATA_VALUE)
.part("image", new byte[0])
.header(HttpHeaders.CONTENT_TYPE, MediaType.IMAGE_PNG_VALUE)
.submittedFileName("documents/images/example.png")
.build());
@RenderedSnippetTest
void multipartPostWithContentType(OperationBuilder operationBuilder, AssertableSnippets snippets)
throws IOException {
new HttpieRequestSnippet(this.commandFormatter).document(operationBuilder.request("http://localhost/upload")
.method("POST")
.header(HttpHeaders.CONTENT_TYPE, MediaType.MULTIPART_FORM_DATA_VALUE)
.part("image", new byte[0])
.header(HttpHeaders.CONTENT_TYPE, MediaType.IMAGE_PNG_VALUE)
.submittedFileName("documents/images/example.png")
.build());
// httpie does not yet support manually set content type by part
String expectedContent = "$ http --multipart POST 'http://localhost/upload'"
+ " 'image'@'documents/images/example.png'";
assertThat(this.generatedSnippets.httpieRequest()).is(codeBlock("bash").withContent(expectedContent));
assertThat(snippets.httpieRequest())
.isCodeBlock((codeBlock) -> codeBlock.withLanguage("bash").content(expectedContent));
}
@Test
public void multipartPost() throws IOException {
new HttpieRequestSnippet(this.commandFormatter)
.document(this.operationBuilder.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());
@RenderedSnippetTest
void multipartPost(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
new HttpieRequestSnippet(this.commandFormatter).document(operationBuilder.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());
String expectedContent = "$ http --multipart POST 'http://localhost/upload'"
+ " 'image'@'documents/images/example.png'";
assertThat(this.generatedSnippets.httpieRequest()).is(codeBlock("bash").withContent(expectedContent));
assertThat(snippets.httpieRequest())
.isCodeBlock((codeBlock) -> codeBlock.withLanguage("bash").content(expectedContent));
}
@Test
public void basicAuthCredentialsAreSuppliedUsingAuthOption() throws IOException {
new HttpieRequestSnippet(this.commandFormatter).document(this.operationBuilder.request("http://localhost/foo")
@RenderedSnippetTest
void basicAuthCredentialsAreSuppliedUsingAuthOption(OperationBuilder operationBuilder, AssertableSnippets snippets)
throws IOException {
new HttpieRequestSnippet(this.commandFormatter).document(operationBuilder.request("http://localhost/foo")
.header(HttpHeaders.AUTHORIZATION, "Basic " + Base64.getEncoder().encodeToString("user:secret".getBytes()))
.build());
assertThat(this.generatedSnippets.httpieRequest())
.is(codeBlock("bash").withContent("$ http --auth 'user:secret' GET 'http://localhost/foo'"));
assertThat(snippets.httpieRequest()).isCodeBlock((codeBlock) -> codeBlock.withLanguage("bash")
.content("$ http --auth 'user:secret' GET 'http://localhost/foo'"));
}
@Test
public void customAttributes() throws IOException {
new HttpieRequestSnippet(this.commandFormatter).document(this.operationBuilder.request("http://localhost/foo")
@RenderedSnippetTest
void customAttributes(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
new HttpieRequestSnippet(this.commandFormatter).document(operationBuilder.request("http://localhost/foo")
.header(HttpHeaders.HOST, "api.example.com")
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
.header("a", "alpha")
.build());
assertThat(this.generatedSnippets.httpieRequest())
.is(codeBlock("bash").withContent("$ http GET 'http://localhost/foo' 'Host:api.example.com'"
assertThat(snippets.httpieRequest()).isCodeBlock((codeBlock) -> codeBlock.withLanguage("bash")
.content("$ http GET 'http://localhost/foo' 'Host:api.example.com'"
+ " 'Content-Type:application/json' 'a:alpha'"));
}
@Test
public void deleteWithQueryString() throws IOException {
@RenderedSnippetTest
void deleteWithQueryString(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
new HttpieRequestSnippet(this.commandFormatter)
.document(this.operationBuilder.request("http://localhost/foo?a=alpha&b=bravo").method("DELETE").build());
assertThat(this.generatedSnippets.httpieRequest())
.is(codeBlock("bash").withContent("$ http DELETE 'http://localhost/foo?a=alpha&b=bravo'"));
.document(operationBuilder.request("http://localhost/foo?a=alpha&b=bravo").method("DELETE").build());
assertThat(snippets.httpieRequest()).isCodeBlock((codeBlock) -> codeBlock.withLanguage("bash")
.content("$ http DELETE 'http://localhost/foo?a=alpha&b=bravo'"));
}
}

View File

@@ -23,7 +23,7 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.junit.Test;
import org.junit.jupiter.api.Test;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
@@ -63,13 +63,13 @@ import static org.mockito.Mockito.mock;
* @author Andy Wilkinson
* @author Filip Hrisafov
*/
public class RestDocumentationConfigurerTests {
class RestDocumentationConfigurerTests {
private final TestRestDocumentationConfigurer configurer = new TestRestDocumentationConfigurer();
@SuppressWarnings("unchecked")
@Test
public void defaultConfiguration() {
void defaultConfiguration() {
Map<String, Object> configuration = new HashMap<>();
this.configurer.apply(configuration, createContext());
assertThat(configuration).containsKey(TemplateEngine.class.getName());
@@ -102,7 +102,7 @@ public class RestDocumentationConfigurerTests {
}
@Test
public void customTemplateEngine() {
void customTemplateEngine() {
Map<String, Object> configuration = new HashMap<>();
TemplateEngine templateEngine = mock(TemplateEngine.class);
this.configurer.templateEngine(templateEngine).apply(configuration, createContext());
@@ -110,7 +110,7 @@ public class RestDocumentationConfigurerTests {
}
@Test
public void customWriterResolver() {
void customWriterResolver() {
Map<String, Object> configuration = new HashMap<>();
WriterResolver writerResolver = mock(WriterResolver.class);
this.configurer.writerResolver(writerResolver).apply(configuration, createContext());
@@ -118,7 +118,7 @@ public class RestDocumentationConfigurerTests {
}
@Test
public void customDefaultSnippets() {
void customDefaultSnippets() {
Map<String, Object> configuration = new HashMap<>();
this.configurer.snippets().withDefaults(CliDocumentation.curlRequest()).apply(configuration, createContext());
assertThat(configuration).containsKey(RestDocumentationGenerator.ATTRIBUTE_NAME_DEFAULT_SNIPPETS);
@@ -133,7 +133,7 @@ public class RestDocumentationConfigurerTests {
@SuppressWarnings("unchecked")
@Test
public void additionalDefaultSnippets() {
void additionalDefaultSnippets() {
Map<String, Object> configuration = new HashMap<>();
Snippet snippet = mock(Snippet.class);
this.configurer.snippets().withAdditionalDefaults(snippet).apply(configuration, createContext());
@@ -148,7 +148,7 @@ public class RestDocumentationConfigurerTests {
}
@Test
public void customSnippetEncoding() {
void customSnippetEncoding() {
Map<String, Object> configuration = new HashMap<>();
this.configurer.snippets().withEncoding("ISO-8859-1");
this.configurer.apply(configuration, createContext());
@@ -162,7 +162,7 @@ public class RestDocumentationConfigurerTests {
}
@Test
public void customTemplateFormat() {
void customTemplateFormat() {
Map<String, Object> configuration = new HashMap<>();
this.configurer.snippets().withTemplateFormat(TemplateFormats.markdown()).apply(configuration, createContext());
assertThat(configuration).containsKey(SnippetConfiguration.class.getName());
@@ -174,7 +174,7 @@ public class RestDocumentationConfigurerTests {
@SuppressWarnings("unchecked")
@Test
public void asciidoctorTableCellContentLambaIsInstalledWhenUsingAsciidoctorTemplateFormat() {
void asciidoctorTableCellContentLambaIsInstalledWhenUsingAsciidoctorTemplateFormat() {
Map<String, Object> configuration = new HashMap<>();
this.configurer.apply(configuration, createContext());
TemplateEngine templateEngine = (TemplateEngine) configuration.get(TemplateEngine.class.getName());
@@ -187,7 +187,7 @@ public class RestDocumentationConfigurerTests {
@SuppressWarnings("unchecked")
@Test
public void asciidoctorTableCellContentLambaIsNotInstalledWhenUsingNonAsciidoctorTemplateFormat() {
void asciidoctorTableCellContentLambaIsNotInstalledWhenUsingNonAsciidoctorTemplateFormat() {
Map<String, Object> configuration = new HashMap<>();
this.configurer.snippetConfigurer.withTemplateFormat(TemplateFormats.markdown());
this.configurer.apply(configuration, createContext());
@@ -199,7 +199,7 @@ public class RestDocumentationConfigurerTests {
}
@Test
public void customDefaultOperationRequestPreprocessor() {
void customDefaultOperationRequestPreprocessor() {
Map<String, Object> configuration = new HashMap<>();
this.configurer.operationPreprocessors()
.withRequestDefaults(Preprocessors.prettyPrint(), Preprocessors.modifyHeaders().remove("Foo"))
@@ -214,7 +214,7 @@ public class RestDocumentationConfigurerTests {
}
@Test
public void customDefaultOperationResponsePreprocessor() {
void customDefaultOperationResponsePreprocessor() {
Map<String, Object> configuration = new HashMap<>();
this.configurer.operationPreprocessors()
.withResponseDefaults(Preprocessors.prettyPrint(), Preprocessors.modifyHeaders().remove("Foo"))

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2024 the original author or authors.
* Copyright 2014-2025 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,7 +19,7 @@ package org.springframework.restdocs.constraints;
import java.util.Arrays;
import java.util.Collections;
import org.junit.Test;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.BDDMockito.given;
@@ -30,7 +30,7 @@ import static org.mockito.Mockito.mock;
*
* @author Andy Wilkinson
*/
public class ConstraintDescriptionsTests {
class ConstraintDescriptionsTests {
private final ConstraintResolver constraintResolver = mock(ConstraintResolver.class);
@@ -41,7 +41,7 @@ public class ConstraintDescriptionsTests {
this.constraintResolver, this.constraintDescriptionResolver);
@Test
public void descriptionsForConstraints() {
void descriptionsForConstraints() {
Constraint constraint1 = new Constraint("constraint1", Collections.<String, Object>emptyMap());
Constraint constraint2 = new Constraint("constraint2", Collections.<String, Object>emptyMap());
given(this.constraintResolver.resolveForProperty("foo", Constrained.class))
@@ -52,7 +52,7 @@ public class ConstraintDescriptionsTests {
}
@Test
public void emptyListOfDescriptionsWhenThereAreNoConstraints() {
void emptyListOfDescriptionsWhenThereAreNoConstraints() {
given(this.constraintResolver.resolveForProperty("foo", Constrained.class))
.willReturn(Collections.<Constraint>emptyList());
assertThat(this.constraintDescriptions.descriptionsForProperty("foo").size()).isEqualTo(0);

View File

@@ -60,7 +60,7 @@ import org.hibernate.validator.constraints.LuhnCheck;
import org.hibernate.validator.constraints.Mod10Check;
import org.hibernate.validator.constraints.Mod11Check;
import org.hibernate.validator.constraints.Range;
import org.junit.Test;
import org.junit.jupiter.api.Test;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.io.ClassPathResource;
@@ -77,183 +77,183 @@ import static org.assertj.core.api.Assertions.assertThat;
*
* @author Andy Wilkinson
*/
public class ResourceBundleConstraintDescriptionResolverTests {
class ResourceBundleConstraintDescriptionResolverTests {
private final ResourceBundleConstraintDescriptionResolver resolver = new ResourceBundleConstraintDescriptionResolver();
@Test
public void defaultMessageAssertFalse() {
void defaultMessageAssertFalse() {
assertThat(constraintDescriptionForField("assertFalse")).isEqualTo("Must be false");
}
@Test
public void defaultMessageAssertTrue() {
void defaultMessageAssertTrue() {
assertThat(constraintDescriptionForField("assertTrue")).isEqualTo("Must be true");
}
@Test
public void defaultMessageCodePointLength() {
void defaultMessageCodePointLength() {
assertThat(constraintDescriptionForField("codePointLength"))
.isEqualTo("Code point length must be between 2 and 5 inclusive");
}
@Test
public void defaultMessageCurrency() {
void defaultMessageCurrency() {
assertThat(constraintDescriptionForField("currency"))
.isEqualTo("Must be in an accepted currency unit (GBP, USD)");
}
@Test
public void defaultMessageDecimalMax() {
void defaultMessageDecimalMax() {
assertThat(constraintDescriptionForField("decimalMax")).isEqualTo("Must be at most 9.875");
}
@Test
public void defaultMessageDecimalMin() {
void defaultMessageDecimalMin() {
assertThat(constraintDescriptionForField("decimalMin")).isEqualTo("Must be at least 1.5");
}
@Test
public void defaultMessageDigits() {
void defaultMessageDigits() {
assertThat(constraintDescriptionForField("digits"))
.isEqualTo("Must have at most 2 integral digits and 5 fractional digits");
}
@Test
public void defaultMessageFuture() {
void defaultMessageFuture() {
assertThat(constraintDescriptionForField("future")).isEqualTo("Must be in the future");
}
@Test
public void defaultMessageFutureOrPresent() {
void defaultMessageFutureOrPresent() {
assertThat(constraintDescriptionForField("futureOrPresent")).isEqualTo("Must be in the future or the present");
}
@Test
public void defaultMessageMax() {
void defaultMessageMax() {
assertThat(constraintDescriptionForField("max")).isEqualTo("Must be at most 10");
}
@Test
public void defaultMessageMin() {
void defaultMessageMin() {
assertThat(constraintDescriptionForField("min")).isEqualTo("Must be at least 10");
}
@Test
public void defaultMessageNotNull() {
void defaultMessageNotNull() {
assertThat(constraintDescriptionForField("notNull")).isEqualTo("Must not be null");
}
@Test
public void defaultMessageNull() {
void defaultMessageNull() {
assertThat(constraintDescriptionForField("nul")).isEqualTo("Must be null");
}
@Test
public void defaultMessagePast() {
void defaultMessagePast() {
assertThat(constraintDescriptionForField("past")).isEqualTo("Must be in the past");
}
@Test
public void defaultMessagePastOrPresent() {
void defaultMessagePastOrPresent() {
assertThat(constraintDescriptionForField("pastOrPresent")).isEqualTo("Must be in the past or the present");
}
@Test
public void defaultMessagePattern() {
void defaultMessagePattern() {
assertThat(constraintDescriptionForField("pattern"))
.isEqualTo("Must match the regular expression `[A-Z][a-z]+`");
}
@Test
public void defaultMessageSize() {
void defaultMessageSize() {
assertThat(constraintDescriptionForField("size")).isEqualTo("Size must be between 2 and 10 inclusive");
}
@Test
public void defaultMessageCreditCardNumber() {
void defaultMessageCreditCardNumber() {
assertThat(constraintDescriptionForField("creditCardNumber"))
.isEqualTo("Must be a well-formed credit card number");
}
@Test
public void defaultMessageEan() {
void defaultMessageEan() {
assertThat(constraintDescriptionForField("ean")).isEqualTo("Must be a well-formed EAN13 number");
}
@Test
public void defaultMessageEmail() {
void defaultMessageEmail() {
assertThat(constraintDescriptionForField("email")).isEqualTo("Must be a well-formed email address");
}
@Test
public void defaultMessageLength() {
void defaultMessageLength() {
assertThat(constraintDescriptionForField("length")).isEqualTo("Length must be between 2 and 10 inclusive");
}
@Test
public void defaultMessageLuhnCheck() {
void defaultMessageLuhnCheck() {
assertThat(constraintDescriptionForField("luhnCheck"))
.isEqualTo("Must pass the Luhn Modulo 10 checksum algorithm");
}
@Test
public void defaultMessageMod10Check() {
void defaultMessageMod10Check() {
assertThat(constraintDescriptionForField("mod10Check")).isEqualTo("Must pass the Mod10 checksum algorithm");
}
@Test
public void defaultMessageMod11Check() {
void defaultMessageMod11Check() {
assertThat(constraintDescriptionForField("mod11Check")).isEqualTo("Must pass the Mod11 checksum algorithm");
}
@Test
public void defaultMessageNegative() {
void defaultMessageNegative() {
assertThat(constraintDescriptionForField("negative")).isEqualTo("Must be negative");
}
@Test
public void defaultMessageNegativeOrZero() {
void defaultMessageNegativeOrZero() {
assertThat(constraintDescriptionForField("negativeOrZero")).isEqualTo("Must be negative or zero");
}
@Test
public void defaultMessageNotBlank() {
void defaultMessageNotBlank() {
assertThat(constraintDescriptionForField("notBlank")).isEqualTo("Must not be blank");
}
@Test
public void defaultMessageNotEmpty() {
void defaultMessageNotEmpty() {
assertThat(constraintDescriptionForField("notEmpty")).isEqualTo("Must not be empty");
}
@Test
public void defaultMessageNotEmptyHibernateValidator() {
void defaultMessageNotEmptyHibernateValidator() {
assertThat(constraintDescriptionForField("notEmpty")).isEqualTo("Must not be empty");
}
@Test
public void defaultMessagePositive() {
void defaultMessagePositive() {
assertThat(constraintDescriptionForField("positive")).isEqualTo("Must be positive");
}
@Test
public void defaultMessagePositiveOrZero() {
void defaultMessagePositiveOrZero() {
assertThat(constraintDescriptionForField("positiveOrZero")).isEqualTo("Must be positive or zero");
}
@Test
public void defaultMessageRange() {
void defaultMessageRange() {
assertThat(constraintDescriptionForField("range")).isEqualTo("Must be at least 10 and at most 100");
}
@Test
public void defaultMessageUrl() {
void defaultMessageUrl() {
assertThat(constraintDescriptionForField("url")).isEqualTo("Must be a well-formed URL");
}
@Test
public void customMessage() {
void customMessage() {
Thread.currentThread().setContextClassLoader(new ClassLoader() {
@Override
@@ -279,7 +279,7 @@ public class ResourceBundleConstraintDescriptionResolverTests {
}
@Test
public void customResourceBundle() {
void customResourceBundle() {
ResourceBundle bundle = new ListResourceBundle() {
@Override
@@ -294,7 +294,7 @@ public class ResourceBundleConstraintDescriptionResolverTests {
}
@Test
public void allBeanValidationConstraintsAreTested() throws Exception {
void allBeanValidationConstraintsAreTested() throws Exception {
PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
Resource[] resources = resolver.getResources("jakarta/validation/constraints/*.class");
Set<Class<?>> beanValidationConstraints = new HashSet<>();

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2024 the original author or authors.
* Copyright 2014-2025 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.
@@ -35,7 +35,7 @@ import org.assertj.core.api.Condition;
import org.assertj.core.description.TextDescription;
import org.hibernate.validator.constraints.CompositionType;
import org.hibernate.validator.constraints.ConstraintComposition;
import org.junit.Test;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
@@ -44,19 +44,19 @@ import static org.assertj.core.api.Assertions.assertThat;
*
* @author Andy Wilkinson
*/
public class ValidatorConstraintResolverTests {
class ValidatorConstraintResolverTests {
private final ValidatorConstraintResolver resolver = new ValidatorConstraintResolver();
@Test
public void singleFieldConstraint() {
void singleFieldConstraint() {
List<Constraint> constraints = this.resolver.resolveForProperty("single", ConstrainedFields.class);
assertThat(constraints).hasSize(1);
assertThat(constraints.get(0).getName()).isEqualTo(NotNull.class.getName());
}
@Test
public void multipleFieldConstraints() {
void multipleFieldConstraints() {
List<Constraint> constraints = this.resolver.resolveForProperty("multiple", ConstrainedFields.class);
assertThat(constraints).hasSize(2);
assertThat(constraints.get(0)).is(constraint(NotNull.class));
@@ -64,13 +64,13 @@ public class ValidatorConstraintResolverTests {
}
@Test
public void noFieldConstraints() {
void noFieldConstraints() {
List<Constraint> constraints = this.resolver.resolveForProperty("none", ConstrainedFields.class);
assertThat(constraints).hasSize(0);
}
@Test
public void compositeConstraint() {
void compositeConstraint() {
List<Constraint> constraints = this.resolver.resolveForProperty("composite", ConstrainedFields.class);
assertThat(constraints).hasSize(1);
}

View File

@@ -1,59 +0,0 @@
/*
* Copyright 2014-2023 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.cookies;
import java.util.Collections;
import org.junit.Rule;
import org.junit.Test;
import org.springframework.restdocs.snippet.SnippetException;
import org.springframework.restdocs.templates.TemplateFormats;
import org.springframework.restdocs.testfixtures.OperationBuilder;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
/**
* Tests for failures when rendering {@link RequestCookiesSnippet} due to missing or
* undocumented cookies.
*
* @author Clyde Stubbs
* @author Andy Wilkinson
*/
public class RequestCookiesSnippetFailureTests {
@Rule
public OperationBuilder operationBuilder = new OperationBuilder(TemplateFormats.asciidoctor());
@Test
public void missingRequestCookie() {
assertThatExceptionOfType(SnippetException.class)
.isThrownBy(() -> new RequestCookiesSnippet(
Collections.singletonList(CookieDocumentation.cookieWithName("JSESSIONID").description("one")))
.document(this.operationBuilder.request("http://localhost").build()))
.withMessage("Cookies with the following names were not found in the request: [JSESSIONID]");
}
@Test
public void undocumentedRequestCookie() {
assertThatExceptionOfType(SnippetException.class)
.isThrownBy(() -> new RequestCookiesSnippet(Collections.emptyList()).document(
this.operationBuilder.request("http://localhost").cookie("JSESSIONID", "1234abcd5678efgh").build()))
.withMessageEndingWith("Cookies with the following names were not documented: [JSESSIONID]");
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2023 the original author or authors.
* Copyright 2014-2025 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.
@@ -20,18 +20,15 @@ import java.io.IOException;
import java.util.Arrays;
import java.util.Collections;
import org.junit.Test;
import org.springframework.restdocs.AbstractSnippetTests;
import org.springframework.restdocs.templates.TemplateEngine;
import org.springframework.restdocs.templates.TemplateFormat;
import org.springframework.restdocs.templates.TemplateFormats;
import org.springframework.restdocs.templates.TemplateResourceResolver;
import org.springframework.restdocs.templates.mustache.MustacheTemplateEngine;
import org.springframework.restdocs.snippet.SnippetException;
import org.springframework.restdocs.testfixtures.jupiter.AssertableSnippets;
import org.springframework.restdocs.testfixtures.jupiter.OperationBuilder;
import org.springframework.restdocs.testfixtures.jupiter.RenderedSnippetTest;
import org.springframework.restdocs.testfixtures.jupiter.SnippetTemplate;
import org.springframework.restdocs.testfixtures.jupiter.SnippetTest;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.springframework.restdocs.cookies.CookieDocumentation.cookieWithName;
import static org.springframework.restdocs.snippet.Attributes.attributes;
import static org.springframework.restdocs.snippet.Attributes.key;
@@ -42,138 +39,141 @@ import static org.springframework.restdocs.snippet.Attributes.key;
* @author Clyde Stubbs
* @author Andy Wilkinson
*/
public class RequestCookiesSnippetTests extends AbstractSnippetTests {
class RequestCookiesSnippetTests {
public RequestCookiesSnippetTests(String name, TemplateFormat templateFormat) {
super(name, templateFormat);
}
@Test
public void requestWithCookies() throws IOException {
@RenderedSnippetTest
void requestWithCookies(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
new RequestCookiesSnippet(
Arrays.asList(cookieWithName("tz").description("one"), cookieWithName("logged_in").description("two")))
.document(this.operationBuilder.request("http://localhost")
.document(operationBuilder.request("http://localhost")
.cookie("tz", "Europe%2FLondon")
.cookie("logged_in", "true")
.build());
assertThat(this.generatedSnippets.requestCookies())
.is(tableWithHeader("Name", "Description").row("`tz`", "one").row("`logged_in`", "two"));
assertThat(snippets.requestCookies())
.isTable((table) -> table.withHeader("Name", "Description").row("`tz`", "one").row("`logged_in`", "two"));
}
@Test
public void ignoredRequestCookie() throws IOException {
@RenderedSnippetTest
void ignoredRequestCookie(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
new RequestCookiesSnippet(
Arrays.asList(cookieWithName("tz").ignored(), cookieWithName("logged_in").description("two")))
.document(this.operationBuilder.request("http://localhost")
.document(operationBuilder.request("http://localhost")
.cookie("tz", "Europe%2FLondon")
.cookie("logged_in", "true")
.build());
assertThat(this.generatedSnippets.requestCookies())
.is(tableWithHeader("Name", "Description").row("`logged_in`", "two"));
assertThat(snippets.requestCookies())
.isTable((table) -> table.withHeader("Name", "Description").row("`logged_in`", "two"));
}
@Test
public void allUndocumentedCookiesCanBeIgnored() throws IOException {
@RenderedSnippetTest
void allUndocumentedCookiesCanBeIgnored(OperationBuilder operationBuilder, AssertableSnippets snippets)
throws IOException {
new RequestCookiesSnippet(
Arrays.asList(cookieWithName("tz").description("one"), cookieWithName("logged_in").description("two")),
true)
.document(this.operationBuilder.request("http://localhost")
.document(operationBuilder.request("http://localhost")
.cookie("tz", "Europe%2FLondon")
.cookie("logged_in", "true")
.cookie("user_session", "abcd1234efgh5678")
.build());
assertThat(this.generatedSnippets.requestCookies())
.is(tableWithHeader("Name", "Description").row("`tz`", "one").row("`logged_in`", "two"));
assertThat(snippets.requestCookies())
.isTable((table) -> table.withHeader("Name", "Description").row("`tz`", "one").row("`logged_in`", "two"));
}
@Test
public void missingOptionalCookie() throws IOException {
@RenderedSnippetTest
void missingOptionalCookie(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
new RequestCookiesSnippet(Arrays.asList(cookieWithName("tz").description("one").optional(),
cookieWithName("logged_in").description("two")))
.document(this.operationBuilder.request("http://localhost").cookie("logged_in", "true").build());
assertThat(this.generatedSnippets.requestCookies())
.is(tableWithHeader("Name", "Description").row("`tz`", "one").row("`logged_in`", "two"));
.document(operationBuilder.request("http://localhost").cookie("logged_in", "true").build());
assertThat(snippets.requestCookies())
.isTable((table) -> table.withHeader("Name", "Description").row("`tz`", "one").row("`logged_in`", "two"));
}
@Test
public void requestCookiesWithCustomAttributes() throws IOException {
TemplateResourceResolver resolver = mock(TemplateResourceResolver.class);
given(resolver.resolveTemplateResource("request-cookies"))
.willReturn(snippetResource("request-cookies-with-title"));
@RenderedSnippetTest
@SnippetTemplate(snippet = "request-cookies", template = "request-cookies-with-title")
void requestCookiesWithCustomAttributes(OperationBuilder operationBuilder, AssertableSnippets snippets)
throws IOException {
new RequestCookiesSnippet(Collections.singletonList(cookieWithName("tz").description("one")),
attributes(key("title").value("Custom title")))
.document(this.operationBuilder
.attribute(TemplateEngine.class.getName(), new MustacheTemplateEngine(resolver))
.request("http://localhost")
.cookie("tz", "Europe%2FLondon")
.build());
assertThat(this.generatedSnippets.requestCookies()).contains("Custom title");
.document(operationBuilder.request("http://localhost").cookie("tz", "Europe%2FLondon").build());
assertThat(snippets.requestCookies()).contains("Custom title");
}
@Test
public void requestCookiesWithCustomDescriptorAttributes() throws IOException {
TemplateResourceResolver resolver = mock(TemplateResourceResolver.class);
given(resolver.resolveTemplateResource("request-cookies"))
.willReturn(snippetResource("request-cookies-with-extra-column"));
@RenderedSnippetTest
@SnippetTemplate(snippet = "request-cookies", template = "request-cookies-with-extra-column")
void requestCookiesWithCustomDescriptorAttributes(OperationBuilder operationBuilder, AssertableSnippets snippets)
throws IOException {
new RequestCookiesSnippet(
Arrays.asList(cookieWithName("tz").description("one").attributes(key("foo").value("alpha")),
cookieWithName("logged_in").description("two").attributes(key("foo").value("bravo"))))
.document(this.operationBuilder
.attribute(TemplateEngine.class.getName(), new MustacheTemplateEngine(resolver))
.request("http://localhost")
.document(operationBuilder.request("http://localhost")
.cookie("tz", "Europe%2FLondon")
.cookie("logged_in", "true")
.build());
assertThat(this.generatedSnippets.requestCookies()).is(//
tableWithHeader("Name", "Description", "Foo").row("tz", "one", "alpha")
.row("logged_in", "two", "bravo"));
assertThat(snippets.requestCookies()).isTable((table) -> table.withHeader("Name", "Description", "Foo")
.row("tz", "one", "alpha")
.row("logged_in", "two", "bravo"));
}
@Test
public void additionalDescriptors() throws IOException {
@RenderedSnippetTest
void additionalDescriptors(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
new RequestCookiesSnippet(
Arrays.asList(cookieWithName("tz").description("one"), cookieWithName("logged_in").description("two")))
.and(cookieWithName("user_session").description("three"))
.document(this.operationBuilder.request("http://localhost")
.document(operationBuilder.request("http://localhost")
.cookie("tz", "Europe%2FLondon")
.cookie("logged_in", "true")
.cookie("user_session", "abcd1234efgh5678")
.build());
assertThat(this.generatedSnippets.requestCookies()).is(tableWithHeader("Name", "Description").row("`tz`", "one")
assertThat(snippets.requestCookies()).isTable((table) -> table.withHeader("Name", "Description")
.row("`tz`", "one")
.row("`logged_in`", "two")
.row("`user_session`", "three"));
}
@Test
public void additionalDescriptorsWithRelaxedRequestCookies() throws IOException {
@RenderedSnippetTest
void additionalDescriptorsWithRelaxedRequestCookies(OperationBuilder operationBuilder, AssertableSnippets snippets)
throws IOException {
new RequestCookiesSnippet(
Arrays.asList(cookieWithName("tz").description("one"), cookieWithName("logged_in").description("two")),
true)
.and(cookieWithName("user_session").description("three"))
.document(this.operationBuilder.request("http://localhost")
.document(operationBuilder.request("http://localhost")
.cookie("tz", "Europe%2FLondon")
.cookie("logged_in", "true")
.cookie("user_session", "abcd1234efgh5678")
.cookie("color_theme", "light")
.build());
assertThat(this.generatedSnippets.requestCookies()).is(tableWithHeader("Name", "Description").row("`tz`", "one")
assertThat(snippets.requestCookies()).isTable((table) -> table.withHeader("Name", "Description")
.row("`tz`", "one")
.row("`logged_in`", "two")
.row("`user_session`", "three"));
}
@Test
public void tableCellContentIsEscapedWhenNecessary() throws IOException {
@RenderedSnippetTest
void tableCellContentIsEscapedWhenNecessary(OperationBuilder operationBuilder, AssertableSnippets snippets)
throws IOException {
new RequestCookiesSnippet(Collections.singletonList(cookieWithName("Foo|Bar").description("one|two")))
.document(this.operationBuilder.request("http://localhost").cookie("Foo|Bar", "baz").build());
assertThat(this.generatedSnippets.requestCookies()).is(tableWithHeader("Name", "Description")
.row(escapeIfNecessary("`Foo|Bar`"), escapeIfNecessary("one|two")));
.document(operationBuilder.request("http://localhost").cookie("Foo|Bar", "baz").build());
assertThat(snippets.requestCookies())
.isTable((table) -> table.withHeader("Name", "Description").row("`Foo|Bar`", "one|two"));
}
private String escapeIfNecessary(String input) {
if (this.templateFormat.getId().equals(TemplateFormats.markdown().getId())) {
return input;
}
return input.replace("|", "\\|");
@SnippetTest
void missingRequestCookie(OperationBuilder operationBuilder) {
assertThatExceptionOfType(SnippetException.class)
.isThrownBy(() -> new RequestCookiesSnippet(
Collections.singletonList(CookieDocumentation.cookieWithName("JSESSIONID").description("one")))
.document(operationBuilder.request("http://localhost").build()))
.withMessage("Cookies with the following names were not found in the request: [JSESSIONID]");
}
@SnippetTest
void undocumentedRequestCookie(OperationBuilder operationBuilder) {
assertThatExceptionOfType(SnippetException.class)
.isThrownBy(() -> new RequestCookiesSnippet(Collections.emptyList()).document(
operationBuilder.request("http://localhost").cookie("JSESSIONID", "1234abcd5678efgh").build()))
.withMessageEndingWith("Cookies with the following names were not documented: [JSESSIONID]");
}
}

View File

@@ -1,60 +0,0 @@
/*
* Copyright 2014-2023 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.cookies;
import java.util.Collections;
import org.junit.Rule;
import org.junit.Test;
import org.springframework.restdocs.snippet.SnippetException;
import org.springframework.restdocs.templates.TemplateFormats;
import org.springframework.restdocs.testfixtures.OperationBuilder;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.springframework.restdocs.cookies.CookieDocumentation.cookieWithName;
/**
* Tests for failures when rendering {@link ResponseCookiesSnippet} due to missing or
* undocumented cookies.
*
* @author Clyde Stubbs
* @author Andy Wilkinson
*/
public class ResponseCookiesSnippetFailureTests {
@Rule
public OperationBuilder operationBuilder = new OperationBuilder(TemplateFormats.asciidoctor());
@Test
public void missingResponseCookie() {
assertThatExceptionOfType(SnippetException.class)
.isThrownBy(() -> new ResponseCookiesSnippet(
Collections.singletonList(cookieWithName("JSESSIONID").description("one")))
.document(this.operationBuilder.response().build()))
.withMessage("Cookies with the following names were not found in the response: [JSESSIONID]");
}
@Test
public void undocumentedResponseCookie() {
assertThatExceptionOfType(SnippetException.class)
.isThrownBy(() -> new ResponseCookiesSnippet(Collections.emptyList())
.document(this.operationBuilder.response().cookie("JSESSIONID", "1234abcd5678efgh").build()))
.withMessageEndingWith("Cookies with the following names were not documented: [JSESSIONID]");
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2023 the original author or authors.
* Copyright 2014-2025 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.
@@ -20,18 +20,12 @@ import java.io.IOException;
import java.util.Arrays;
import java.util.Collections;
import org.junit.Test;
import org.springframework.restdocs.AbstractSnippetTests;
import org.springframework.restdocs.templates.TemplateEngine;
import org.springframework.restdocs.templates.TemplateFormat;
import org.springframework.restdocs.templates.TemplateFormats;
import org.springframework.restdocs.templates.TemplateResourceResolver;
import org.springframework.restdocs.templates.mustache.MustacheTemplateEngine;
import org.springframework.restdocs.testfixtures.jupiter.AssertableSnippets;
import org.springframework.restdocs.testfixtures.jupiter.OperationBuilder;
import org.springframework.restdocs.testfixtures.jupiter.RenderedSnippetTest;
import org.springframework.restdocs.testfixtures.jupiter.SnippetTemplate;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
import static org.springframework.restdocs.cookies.CookieDocumentation.cookieWithName;
import static org.springframework.restdocs.snippet.Attributes.attributes;
import static org.springframework.restdocs.snippet.Attributes.key;
@@ -42,141 +36,127 @@ import static org.springframework.restdocs.snippet.Attributes.key;
* @author Clyde Stubbs
* @author Andy Wilkinson
*/
public class ResponseCookiesSnippetTests extends AbstractSnippetTests {
class ResponseCookiesSnippetTests {
public ResponseCookiesSnippetTests(String name, TemplateFormat templateFormat) {
super(name, templateFormat);
}
@Test
public void responseWithCookies() throws IOException {
@RenderedSnippetTest
void responseWithCookies(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
new ResponseCookiesSnippet(Arrays.asList(cookieWithName("has_recent_activity").description("one"),
cookieWithName("user_session").description("two")))
.document(this.operationBuilder.response()
.document(operationBuilder.response()
.cookie("has_recent_activity", "true")
.cookie("user_session", "1234abcd5678efgh")
.build());
assertThat(this.generatedSnippets.responseCookies())
.is(tableWithHeader("Name", "Description").row("`has_recent_activity`", "one")
.row("`user_session`", "two"));
assertThat(snippets.responseCookies()).isTable((table) -> table.withHeader("Name", "Description")
.row("`has_recent_activity`", "one")
.row("`user_session`", "two"));
}
@Test
public void ignoredResponseCookie() throws IOException {
@RenderedSnippetTest
void ignoredResponseCookie(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
new ResponseCookiesSnippet(Arrays.asList(cookieWithName("has_recent_activity").ignored(),
cookieWithName("user_session").description("two")))
.document(this.operationBuilder.response()
.document(operationBuilder.response()
.cookie("has_recent_activity", "true")
.cookie("user_session", "1234abcd5678efgh")
.build());
assertThat(this.generatedSnippets.responseCookies())
.is(tableWithHeader("Name", "Description").row("`user_session`", "two"));
assertThat(snippets.responseCookies())
.isTable((table) -> table.withHeader("Name", "Description").row("`user_session`", "two"));
}
@Test
public void allUndocumentedResponseCookiesCanBeIgnored() throws IOException {
@RenderedSnippetTest
void allUndocumentedResponseCookiesCanBeIgnored(OperationBuilder operationBuilder, AssertableSnippets snippets)
throws IOException {
new ResponseCookiesSnippet(Arrays.asList(cookieWithName("has_recent_activity").description("one"),
cookieWithName("user_session").description("two")), true)
.document(this.operationBuilder.response()
.document(operationBuilder.response()
.cookie("has_recent_activity", "true")
.cookie("user_session", "1234abcd5678efgh")
.cookie("some_cookie", "value")
.build());
assertThat(this.generatedSnippets.responseCookies())
.is(tableWithHeader("Name", "Description").row("`has_recent_activity`", "one")
.row("`user_session`", "two"));
assertThat(snippets.responseCookies()).isTable((table) -> table.withHeader("Name", "Description")
.row("`has_recent_activity`", "one")
.row("`user_session`", "two"));
}
@Test
public void missingOptionalResponseCookie() throws IOException {
@RenderedSnippetTest
void missingOptionalResponseCookie(OperationBuilder operationBuilder, AssertableSnippets snippets)
throws IOException {
new ResponseCookiesSnippet(Arrays.asList(cookieWithName("has_recent_activity").description("one").optional(),
cookieWithName("user_session").description("two")))
.document(this.operationBuilder.response().cookie("user_session", "1234abcd5678efgh").build());
assertThat(this.generatedSnippets.responseCookies())
.is(tableWithHeader("Name", "Description").row("`has_recent_activity`", "one")
.row("`user_session`", "two"));
.document(operationBuilder.response().cookie("user_session", "1234abcd5678efgh").build());
assertThat(snippets.responseCookies()).isTable((table) -> table.withHeader("Name", "Description")
.row("`has_recent_activity`", "one")
.row("`user_session`", "two"));
}
@Test
public void responseCookiesWithCustomAttributes() throws IOException {
TemplateResourceResolver resolver = mock(TemplateResourceResolver.class);
given(resolver.resolveTemplateResource("response-cookies"))
.willReturn(snippetResource("response-cookies-with-title"));
@RenderedSnippetTest
@SnippetTemplate(snippet = "response-cookies", template = "response-cookies-with-title")
void responseCookiesWithCustomAttributes(OperationBuilder operationBuilder, AssertableSnippets snippets)
throws IOException {
new ResponseCookiesSnippet(Collections.singletonList(cookieWithName("has_recent_activity").description("one")),
attributes(key("title").value("Custom title")))
.document(this.operationBuilder
.attribute(TemplateEngine.class.getName(), new MustacheTemplateEngine(resolver))
.response()
.cookie("has_recent_activity", "true")
.build());
assertThat(this.generatedSnippets.responseCookies()).contains("Custom title");
.document(operationBuilder.response().cookie("has_recent_activity", "true").build());
assertThat(snippets.responseCookies()).contains("Custom title");
}
@Test
public void responseCookiesWithCustomDescriptorAttributes() throws IOException {
TemplateResourceResolver resolver = mock(TemplateResourceResolver.class);
given(resolver.resolveTemplateResource("response-cookies"))
.willReturn(snippetResource("response-cookies-with-extra-column"));
@RenderedSnippetTest
@SnippetTemplate(snippet = "response-cookies", template = "response-cookies-with-extra-column")
void responseCookiesWithCustomDescriptorAttributes(OperationBuilder operationBuilder, AssertableSnippets snippets)
throws IOException {
new ResponseCookiesSnippet(Arrays.asList(
cookieWithName("has_recent_activity").description("one").attributes(key("foo").value("alpha")),
cookieWithName("user_session").description("two").attributes(key("foo").value("bravo")),
cookieWithName("color_theme").description("three").attributes(key("foo").value("charlie"))))
.document(this.operationBuilder
.attribute(TemplateEngine.class.getName(), new MustacheTemplateEngine(resolver))
.response()
.document(operationBuilder.response()
.cookie("has_recent_activity", "true")
.cookie("user_session", "1234abcd5678efgh")
.cookie("color_theme", "high_contrast")
.build());
assertThat(this.generatedSnippets.responseCookies())
.is(tableWithHeader("Name", "Description", "Foo").row("has_recent_activity", "one", "alpha")
.row("user_session", "two", "bravo")
.row("color_theme", "three", "charlie"));
assertThat(snippets.responseCookies()).isTable((table) -> table.withHeader("Name", "Description", "Foo")
.row("has_recent_activity", "one", "alpha")
.row("user_session", "two", "bravo")
.row("color_theme", "three", "charlie"));
}
@Test
public void additionalDescriptors() throws IOException {
@RenderedSnippetTest
void additionalDescriptors(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
CookieDocumentation
.responseCookies(cookieWithName("has_recent_activity").description("one"),
cookieWithName("user_session").description("two"))
.and(cookieWithName("color_theme").description("three"))
.document(this.operationBuilder.response()
.document(operationBuilder.response()
.cookie("has_recent_activity", "true")
.cookie("user_session", "1234abcd5678efgh")
.cookie("color_theme", "light")
.build());
assertThat(this.generatedSnippets.responseCookies())
.is(tableWithHeader("Name", "Description").row("`has_recent_activity`", "one")
.row("`user_session`", "two")
.row("`color_theme`", "three"));
assertThat(snippets.responseCookies()).isTable((table) -> table.withHeader("Name", "Description")
.row("`has_recent_activity`", "one")
.row("`user_session`", "two")
.row("`color_theme`", "three"));
}
@Test
public void additionalDescriptorsWithRelaxedResponseCookies() throws IOException {
@RenderedSnippetTest
void additionalDescriptorsWithRelaxedResponseCookies(OperationBuilder operationBuilder, AssertableSnippets snippets)
throws IOException {
CookieDocumentation.relaxedResponseCookies(cookieWithName("has_recent_activity").description("one"))
.and(cookieWithName("color_theme").description("two"))
.document(this.operationBuilder.response()
.document(operationBuilder.response()
.cookie("has_recent_activity", "true")
.cookie("user_session", "1234abcd5678efgh")
.cookie("color_theme", "light")
.build());
assertThat(this.generatedSnippets.responseCookies())
.is(tableWithHeader("Name", "Description").row("`has_recent_activity`", "one").row("`color_theme`", "two"));
assertThat(snippets.responseCookies()).isTable((table) -> table.withHeader("Name", "Description")
.row("`has_recent_activity`", "one")
.row("`color_theme`", "two"));
}
@Test
public void tableCellContentIsEscapedWhenNecessary() throws IOException {
@RenderedSnippetTest
void tableCellContentIsEscapedWhenNecessary(OperationBuilder operationBuilder, AssertableSnippets snippets)
throws IOException {
new ResponseCookiesSnippet(Collections.singletonList(cookieWithName("Foo|Bar").description("one|two")))
.document(this.operationBuilder.response().cookie("Foo|Bar", "baz").build());
assertThat(this.generatedSnippets.responseCookies()).is(tableWithHeader("Name", "Description")
.row(escapeIfNecessary("`Foo|Bar`"), escapeIfNecessary("one|two")));
}
private String escapeIfNecessary(String input) {
if (this.templateFormat.getId().equals(TemplateFormats.markdown().getId())) {
return input;
}
return input.replace("|", "\\|");
.document(operationBuilder.response().cookie("Foo|Bar", "baz").build());
assertThat(snippets.responseCookies())
.isTable((table) -> table.withHeader("Name", "Description").row("`Foo|Bar`", "one|two"));
}
}

View File

@@ -1,59 +0,0 @@
/*
* Copyright 2014-2023 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.headers;
import java.util.Arrays;
import org.junit.Rule;
import org.junit.Test;
import org.springframework.restdocs.snippet.SnippetException;
import org.springframework.restdocs.templates.TemplateFormats;
import org.springframework.restdocs.testfixtures.OperationBuilder;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
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 OperationBuilder operationBuilder = new OperationBuilder(TemplateFormats.asciidoctor());
@Test
public void missingRequestHeader() {
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() {
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-2023 the original author or authors.
* Copyright 2014-2025 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,18 +19,15 @@ package org.springframework.restdocs.headers;
import java.io.IOException;
import java.util.Arrays;
import org.junit.Test;
import org.springframework.restdocs.AbstractSnippetTests;
import org.springframework.restdocs.templates.TemplateEngine;
import org.springframework.restdocs.templates.TemplateFormat;
import org.springframework.restdocs.templates.TemplateFormats;
import org.springframework.restdocs.templates.TemplateResourceResolver;
import org.springframework.restdocs.templates.mustache.MustacheTemplateEngine;
import org.springframework.restdocs.snippet.SnippetException;
import org.springframework.restdocs.testfixtures.jupiter.AssertableSnippets;
import org.springframework.restdocs.testfixtures.jupiter.OperationBuilder;
import org.springframework.restdocs.testfixtures.jupiter.RenderedSnippetTest;
import org.springframework.restdocs.testfixtures.jupiter.SnippetTemplate;
import org.springframework.restdocs.testfixtures.jupiter.SnippetTest;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.springframework.restdocs.headers.HeaderDocumentation.headerWithName;
import static org.springframework.restdocs.snippet.Attributes.attributes;
import static org.springframework.restdocs.snippet.Attributes.key;
@@ -41,19 +38,15 @@ import static org.springframework.restdocs.snippet.Attributes.key;
* @author Andreas Evers
* @author Andy Wilkinson
*/
public class RequestHeadersSnippetTests extends AbstractSnippetTests {
class RequestHeadersSnippetTests {
public RequestHeadersSnippetTests(String name, TemplateFormat templateFormat) {
super(name, templateFormat);
}
@Test
public void requestWithHeaders() throws IOException {
@RenderedSnippetTest
void requestWithHeaders(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
new RequestHeadersSnippet(Arrays.asList(headerWithName("X-Test").description("one"),
headerWithName("Accept").description("two"), headerWithName("Accept-Encoding").description("three"),
headerWithName("Accept-Language").description("four"),
headerWithName("Cache-Control").description("five"), headerWithName("Connection").description("six")))
.document(this.operationBuilder.request("http://localhost")
.document(operationBuilder.request("http://localhost")
.header("X-Test", "test")
.header("Accept", "*/*")
.header("Accept-Encoding", "gzip, deflate")
@@ -61,79 +54,69 @@ public class RequestHeadersSnippetTests extends AbstractSnippetTests {
.header("Cache-Control", "max-age=0")
.header("Connection", "keep-alive")
.build());
assertThat(this.generatedSnippets.requestHeaders())
.is(tableWithHeader("Name", "Description").row("`X-Test`", "one")
.row("`Accept`", "two")
.row("`Accept-Encoding`", "three")
.row("`Accept-Language`", "four")
.row("`Cache-Control`", "five")
.row("`Connection`", "six"));
assertThat(snippets.requestHeaders()).isTable((table) -> table.withHeader("Name", "Description")
.row("`X-Test`", "one")
.row("`Accept`", "two")
.row("`Accept-Encoding`", "three")
.row("`Accept-Language`", "four")
.row("`Cache-Control`", "five")
.row("`Connection`", "six"));
}
@Test
public void caseInsensitiveRequestHeaders() throws IOException {
@RenderedSnippetTest
void caseInsensitiveRequestHeaders(OperationBuilder operationBuilder, AssertableSnippets snippets)
throws IOException {
new RequestHeadersSnippet(Arrays.asList(headerWithName("X-Test").description("one")))
.document(this.operationBuilder.request("/").header("X-test", "test").build());
assertThat(this.generatedSnippets.requestHeaders())
.is(tableWithHeader("Name", "Description").row("`X-Test`", "one"));
.document(operationBuilder.request("/").header("X-test", "test").build());
assertThat(snippets.requestHeaders())
.isTable((table) -> table.withHeader("Name", "Description").row("`X-Test`", "one"));
}
@Test
public void undocumentedRequestHeader() throws IOException {
new RequestHeadersSnippet(Arrays.asList(headerWithName("X-Test").description("one")))
.document(this.operationBuilder.request("http://localhost")
.header("X-Test", "test")
.header("Accept", "*/*")
.build());
assertThat(this.generatedSnippets.requestHeaders())
.is(tableWithHeader("Name", "Description").row("`X-Test`", "one"));
@RenderedSnippetTest
void undocumentedRequestHeader(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
new RequestHeadersSnippet(Arrays.asList(headerWithName("X-Test").description("one"))).document(
operationBuilder.request("http://localhost").header("X-Test", "test").header("Accept", "*/*").build());
assertThat(snippets.requestHeaders())
.isTable((table) -> table.withHeader("Name", "Description").row("`X-Test`", "one"));
}
@Test
public void requestHeadersWithCustomAttributes() throws IOException {
TemplateResourceResolver resolver = mock(TemplateResourceResolver.class);
given(resolver.resolveTemplateResource("request-headers"))
.willReturn(snippetResource("request-headers-with-title"));
@RenderedSnippetTest
@SnippetTemplate(snippet = "request-headers", template = "request-headers-with-title")
void requestHeadersWithCustomAttributes(OperationBuilder operationBuilder, AssertableSnippets snippets)
throws IOException {
new RequestHeadersSnippet(Arrays.asList(headerWithName("X-Test").description("one")),
attributes(key("title").value("Custom title")))
.document(this.operationBuilder
.attribute(TemplateEngine.class.getName(), new MustacheTemplateEngine(resolver))
.request("http://localhost")
.header("X-Test", "test")
.build());
assertThat(this.generatedSnippets.requestHeaders()).contains("Custom title");
.document(operationBuilder.request("http://localhost").header("X-Test", "test").build());
assertThat(snippets.requestHeaders()).contains("Custom title");
}
@Test
public void requestHeadersWithCustomDescriptorAttributes() throws IOException {
TemplateResourceResolver resolver = mock(TemplateResourceResolver.class);
given(resolver.resolveTemplateResource("request-headers"))
.willReturn(snippetResource("request-headers-with-extra-column"));
@RenderedSnippetTest
@SnippetTemplate(snippet = "request-headers", template = "request-headers-with-extra-column")
void requestHeadersWithCustomDescriptorAttributes(OperationBuilder operationBuilder, AssertableSnippets snippets)
throws IOException {
new RequestHeadersSnippet(
Arrays.asList(headerWithName("X-Test").description("one").attributes(key("foo").value("alpha")),
headerWithName("Accept-Encoding").description("two").attributes(key("foo").value("bravo")),
headerWithName("Accept").description("three").attributes(key("foo").value("charlie"))))
.document(this.operationBuilder
.attribute(TemplateEngine.class.getName(), new MustacheTemplateEngine(resolver))
.request("http://localhost")
.document(operationBuilder.request("http://localhost")
.header("X-Test", "test")
.header("Accept-Encoding", "gzip, deflate")
.header("Accept", "*/*")
.build());
assertThat(this.generatedSnippets.requestHeaders()).is(//
tableWithHeader("Name", "Description", "Foo").row("X-Test", "one", "alpha")
.row("Accept-Encoding", "two", "bravo")
.row("Accept", "three", "charlie"));
assertThat(snippets.requestHeaders()).isTable((table) -> table.withHeader("Name", "Description", "Foo")
.row("X-Test", "one", "alpha")
.row("Accept-Encoding", "two", "bravo")
.row("Accept", "three", "charlie"));
}
@Test
public void additionalDescriptors() throws IOException {
@RenderedSnippetTest
void additionalDescriptors(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
HeaderDocumentation
.requestHeaders(headerWithName("X-Test").description("one"), headerWithName("Accept").description("two"),
headerWithName("Accept-Encoding").description("three"),
headerWithName("Accept-Language").description("four"))
.and(headerWithName("Cache-Control").description("five"), headerWithName("Connection").description("six"))
.document(this.operationBuilder.request("http://localhost")
.document(operationBuilder.request("http://localhost")
.header("X-Test", "test")
.header("Accept", "*/*")
.header("Accept-Encoding", "gzip, deflate")
@@ -141,28 +124,39 @@ public class RequestHeadersSnippetTests extends AbstractSnippetTests {
.header("Cache-Control", "max-age=0")
.header("Connection", "keep-alive")
.build());
assertThat(this.generatedSnippets.requestHeaders())
.is(tableWithHeader("Name", "Description").row("`X-Test`", "one")
.row("`Accept`", "two")
.row("`Accept-Encoding`", "three")
.row("`Accept-Language`", "four")
.row("`Cache-Control`", "five")
.row("`Connection`", "six"));
assertThat(snippets.requestHeaders()).isTable((table) -> table.withHeader("Name", "Description")
.row("`X-Test`", "one")
.row("`Accept`", "two")
.row("`Accept-Encoding`", "three")
.row("`Accept-Language`", "four")
.row("`Cache-Control`", "five")
.row("`Connection`", "six"));
}
@Test
public void tableCellContentIsEscapedWhenNecessary() throws IOException {
@RenderedSnippetTest
void tableCellContentIsEscapedWhenNecessary(OperationBuilder operationBuilder, AssertableSnippets snippets)
throws IOException {
new RequestHeadersSnippet(Arrays.asList(headerWithName("Foo|Bar").description("one|two")))
.document(this.operationBuilder.request("http://localhost").header("Foo|Bar", "baz").build());
assertThat(this.generatedSnippets.requestHeaders()).is(tableWithHeader("Name", "Description")
.row(escapeIfNecessary("`Foo|Bar`"), escapeIfNecessary("one|two")));
.document(operationBuilder.request("http://localhost").header("Foo|Bar", "baz").build());
assertThat(snippets.requestHeaders())
.isTable((table) -> table.withHeader("Name", "Description").row("`Foo|Bar`", "one|two"));
}
private String escapeIfNecessary(String input) {
if (this.templateFormat.getId().equals(TemplateFormats.markdown().getId())) {
return input;
}
return input.replace("|", "\\|");
@SnippetTest
void missingRequestHeader(OperationBuilder operationBuilder) {
assertThatExceptionOfType(SnippetException.class)
.isThrownBy(() -> new RequestHeadersSnippet(Arrays.asList(headerWithName("Accept").description("one")))
.document(operationBuilder.request("http://localhost").build()))
.withMessage("Headers with the following names were not found in the request: [Accept]");
}
@SnippetTest
void undocumentedRequestHeaderAndMissingRequestHeader(OperationBuilder operationBuilder) {
assertThatExceptionOfType(SnippetException.class)
.isThrownBy(() -> new RequestHeadersSnippet(Arrays.asList(headerWithName("Accept").description("one")))
.document(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,60 +0,0 @@
/*
* Copyright 2014-2023 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.headers;
import java.util.Arrays;
import org.junit.Rule;
import org.junit.Test;
import org.springframework.restdocs.snippet.SnippetException;
import org.springframework.restdocs.templates.TemplateFormats;
import org.springframework.restdocs.testfixtures.OperationBuilder;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
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 OperationBuilder operationBuilder = new OperationBuilder(TemplateFormats.asciidoctor());
@Test
public void missingResponseHeader() {
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() {
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-2023 the original author or authors.
* Copyright 2014-2025 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,18 +19,15 @@ package org.springframework.restdocs.headers;
import java.io.IOException;
import java.util.Arrays;
import org.junit.Test;
import org.springframework.restdocs.AbstractSnippetTests;
import org.springframework.restdocs.templates.TemplateEngine;
import org.springframework.restdocs.templates.TemplateFormat;
import org.springframework.restdocs.templates.TemplateFormats;
import org.springframework.restdocs.templates.TemplateResourceResolver;
import org.springframework.restdocs.templates.mustache.MustacheTemplateEngine;
import org.springframework.restdocs.snippet.SnippetException;
import org.springframework.restdocs.testfixtures.jupiter.AssertableSnippets;
import org.springframework.restdocs.testfixtures.jupiter.OperationBuilder;
import org.springframework.restdocs.testfixtures.jupiter.RenderedSnippetTest;
import org.springframework.restdocs.testfixtures.jupiter.SnippetTemplate;
import org.springframework.restdocs.testfixtures.jupiter.SnippetTest;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.springframework.restdocs.headers.HeaderDocumentation.headerWithName;
import static org.springframework.restdocs.snippet.Attributes.attributes;
import static org.springframework.restdocs.snippet.Attributes.key;
@@ -41,119 +38,120 @@ import static org.springframework.restdocs.snippet.Attributes.key;
* @author Andreas Evers
* @author Andy Wilkinson
*/
public class ResponseHeadersSnippetTests extends AbstractSnippetTests {
class ResponseHeadersSnippetTests {
public ResponseHeadersSnippetTests(String name, TemplateFormat templateFormat) {
super(name, templateFormat);
}
@Test
public void responseWithHeaders() throws IOException {
@RenderedSnippetTest
void responseWithHeaders(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
new ResponseHeadersSnippet(Arrays.asList(headerWithName("X-Test").description("one"),
headerWithName("Content-Type").description("two"), headerWithName("Etag").description("three"),
headerWithName("Cache-Control").description("five"), headerWithName("Vary").description("six")))
.document(this.operationBuilder.response()
.document(operationBuilder.response()
.header("X-Test", "test")
.header("Content-Type", "application/json")
.header("Etag", "lskjadldj3ii32l2ij23")
.header("Cache-Control", "max-age=0")
.header("Vary", "User-Agent")
.build());
assertThat(this.generatedSnippets.responseHeaders())
.is(tableWithHeader("Name", "Description").row("`X-Test`", "one")
.row("`Content-Type`", "two")
.row("`Etag`", "three")
.row("`Cache-Control`", "five")
.row("`Vary`", "six"));
assertThat(snippets.responseHeaders()).isTable((table) -> table.withHeader("Name", "Description")
.row("`X-Test`", "one")
.row("`Content-Type`", "two")
.row("`Etag`", "three")
.row("`Cache-Control`", "five")
.row("`Vary`", "six"));
}
@Test
public void caseInsensitiveResponseHeaders() throws IOException {
@RenderedSnippetTest
void caseInsensitiveResponseHeaders(OperationBuilder operationBuilder, AssertableSnippets snippets)
throws IOException {
new ResponseHeadersSnippet(Arrays.asList(headerWithName("X-Test").description("one")))
.document(this.operationBuilder.response().header("X-test", "test").build());
assertThat(this.generatedSnippets.responseHeaders())
.is(tableWithHeader("Name", "Description").row("`X-Test`", "one"));
.document(operationBuilder.response().header("X-test", "test").build());
assertThat(snippets.responseHeaders())
.isTable((table) -> table.withHeader("Name", "Description").row("`X-Test`", "one"));
}
@Test
public void undocumentedResponseHeader() throws IOException {
@RenderedSnippetTest
void undocumentedResponseHeader(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
new ResponseHeadersSnippet(Arrays.asList(headerWithName("X-Test").description("one")))
.document(this.operationBuilder.response().header("X-Test", "test").header("Content-Type", "*/*").build());
assertThat(this.generatedSnippets.responseHeaders())
.is(tableWithHeader("Name", "Description").row("`X-Test`", "one"));
.document(operationBuilder.response().header("X-Test", "test").header("Content-Type", "*/*").build());
assertThat(snippets.responseHeaders())
.isTable((table) -> table.withHeader("Name", "Description").row("`X-Test`", "one"));
}
@Test
public void responseHeadersWithCustomAttributes() throws IOException {
TemplateResourceResolver resolver = mock(TemplateResourceResolver.class);
given(resolver.resolveTemplateResource("response-headers"))
.willReturn(snippetResource("response-headers-with-title"));
@RenderedSnippetTest
@SnippetTemplate(snippet = "response-headers", template = "response-headers-with-title")
void responseHeadersWithCustomAttributes(OperationBuilder operationBuilder, AssertableSnippets snippets)
throws IOException {
new ResponseHeadersSnippet(Arrays.asList(headerWithName("X-Test").description("one")),
attributes(key("title").value("Custom title")))
.document(this.operationBuilder
.attribute(TemplateEngine.class.getName(), new MustacheTemplateEngine(resolver))
.response()
.header("X-Test", "test")
.build());
assertThat(this.generatedSnippets.responseHeaders()).contains("Custom title");
.document(operationBuilder.response().header("X-Test", "test").build());
assertThat(snippets.responseHeaders()).contains("Custom title");
}
@Test
public void responseHeadersWithCustomDescriptorAttributes() throws IOException {
TemplateResourceResolver resolver = mock(TemplateResourceResolver.class);
given(resolver.resolveTemplateResource("response-headers"))
.willReturn(snippetResource("response-headers-with-extra-column"));
@RenderedSnippetTest
@SnippetTemplate(snippet = "response-headers", template = "response-headers-with-extra-column")
void responseHeadersWithCustomDescriptorAttributes(OperationBuilder operationBuilder, AssertableSnippets snippets)
throws IOException {
new ResponseHeadersSnippet(
Arrays.asList(headerWithName("X-Test").description("one").attributes(key("foo").value("alpha")),
headerWithName("Content-Type").description("two").attributes(key("foo").value("bravo")),
headerWithName("Etag").description("three").attributes(key("foo").value("charlie"))))
.document(this.operationBuilder
.attribute(TemplateEngine.class.getName(), new MustacheTemplateEngine(resolver))
.response()
.document(operationBuilder.response()
.header("X-Test", "test")
.header("Content-Type", "application/json")
.header("Etag", "lskjadldj3ii32l2ij23")
.build());
assertThat(this.generatedSnippets.responseHeaders())
.is(tableWithHeader("Name", "Description", "Foo").row("X-Test", "one", "alpha")
.row("Content-Type", "two", "bravo")
.row("Etag", "three", "charlie"));
assertThat(snippets.responseHeaders()).isTable((table) -> table.withHeader("Name", "Description", "Foo")
.row("X-Test", "one", "alpha")
.row("Content-Type", "two", "bravo")
.row("Etag", "three", "charlie"));
}
@Test
public void additionalDescriptors() throws IOException {
@RenderedSnippetTest
void additionalDescriptors(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
HeaderDocumentation
.responseHeaders(headerWithName("X-Test").description("one"),
headerWithName("Content-Type").description("two"), headerWithName("Etag").description("three"))
.and(headerWithName("Cache-Control").description("five"), headerWithName("Vary").description("six"))
.document(this.operationBuilder.response()
.document(operationBuilder.response()
.header("X-Test", "test")
.header("Content-Type", "application/json")
.header("Etag", "lskjadldj3ii32l2ij23")
.header("Cache-Control", "max-age=0")
.header("Vary", "User-Agent")
.build());
assertThat(this.generatedSnippets.responseHeaders())
.is(tableWithHeader("Name", "Description").row("`X-Test`", "one")
.row("`Content-Type`", "two")
.row("`Etag`", "three")
.row("`Cache-Control`", "five")
.row("`Vary`", "six"));
assertThat(snippets.responseHeaders()).isTable((table) -> table.withHeader("Name", "Description")
.row("`X-Test`", "one")
.row("`Content-Type`", "two")
.row("`Etag`", "three")
.row("`Cache-Control`", "five")
.row("`Vary`", "six"));
}
@Test
public void tableCellContentIsEscapedWhenNecessary() throws IOException {
@RenderedSnippetTest
void tableCellContentIsEscapedWhenNecessary(OperationBuilder operationBuilder, AssertableSnippets snippets)
throws IOException {
new ResponseHeadersSnippet(Arrays.asList(headerWithName("Foo|Bar").description("one|two")))
.document(this.operationBuilder.response().header("Foo|Bar", "baz").build());
assertThat(this.generatedSnippets.responseHeaders()).is(tableWithHeader("Name", "Description")
.row(escapeIfNecessary("`Foo|Bar`"), escapeIfNecessary("one|two")));
.document(operationBuilder.response().header("Foo|Bar", "baz").build());
assertThat(snippets.responseHeaders())
.isTable((table) -> table.withHeader("Name", "Description").row("`Foo|Bar`", "one|two"));
}
private String escapeIfNecessary(String input) {
if (this.templateFormat.getId().equals(TemplateFormats.markdown().getId())) {
return input;
}
return input.replace("|", "\\|");
@SnippetTest
void missingResponseHeader(OperationBuilder operationBuilder) {
assertThatExceptionOfType(SnippetException.class)
.isThrownBy(
() -> new ResponseHeadersSnippet(Arrays.asList(headerWithName("Content-Type").description("one")))
.document(operationBuilder.response().build()))
.withMessage("Headers with the following names were not found" + " in the response: [Content-Type]");
}
@SnippetTest
void undocumentedResponseHeaderAndMissingResponseHeader(OperationBuilder operationBuilder) {
assertThatExceptionOfType(SnippetException.class)
.isThrownBy(
() -> new ResponseHeadersSnippet(Arrays.asList(headerWithName("Content-Type").description("one")))
.document(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-2024 the original author or authors.
* Copyright 2014-2025 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,20 +18,14 @@ package org.springframework.restdocs.http;
import java.io.IOException;
import org.junit.Test;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.restdocs.AbstractSnippetTests;
import org.springframework.restdocs.templates.TemplateEngine;
import org.springframework.restdocs.templates.TemplateFormat;
import org.springframework.restdocs.templates.TemplateResourceResolver;
import org.springframework.restdocs.templates.mustache.MustacheTemplateEngine;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.restdocs.testfixtures.jupiter.AssertableSnippets;
import org.springframework.restdocs.testfixtures.jupiter.OperationBuilder;
import org.springframework.restdocs.testfixtures.jupiter.RenderedSnippetTest;
import org.springframework.restdocs.testfixtures.jupiter.SnippetTemplate;
import static org.assertj.core.api.Assertions.assertThat;
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;
@@ -41,162 +35,160 @@ import static org.springframework.restdocs.snippet.Attributes.key;
* @author Andy Wilkinson
* @author Jonathan Pearlin
*/
public class HttpRequestSnippetTests extends AbstractSnippetTests {
class HttpRequestSnippetTests {
private static final String BOUNDARY = "6o2knFse3p53ty9dmcQvWAIx1zInP11uCfbm";
public HttpRequestSnippetTests(String name, TemplateFormat templateFormat) {
super(name, templateFormat);
}
@Test
public void getRequest() throws IOException {
@RenderedSnippetTest
void getRequest(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
new HttpRequestSnippet()
.document(this.operationBuilder.request("http://localhost/foo").header("Alpha", "a").build());
assertThat(this.generatedSnippets.httpRequest())
.is(httpRequest(RequestMethod.GET, "/foo").header("Alpha", "a").header(HttpHeaders.HOST, "localhost"));
.document(operationBuilder.request("http://localhost/foo").header("Alpha", "a").build());
assertThat(snippets.httpRequest())
.isHttpRequest((request) -> request.get("/foo").header("Alpha", "a").header(HttpHeaders.HOST, "localhost"));
}
@Test
public void getRequestWithQueryParameters() throws IOException {
@RenderedSnippetTest
void getRequestWithQueryParameters(OperationBuilder operationBuilder, AssertableSnippets snippets)
throws IOException {
new HttpRequestSnippet()
.document(this.operationBuilder.request("http://localhost/foo?b=bravo").header("Alpha", "a").build());
assertThat(this.generatedSnippets.httpRequest())
.is(httpRequest(RequestMethod.GET, "/foo?b=bravo").header("Alpha", "a")
.header(HttpHeaders.HOST, "localhost"));
.document(operationBuilder.request("http://localhost/foo?b=bravo").header("Alpha", "a").build());
assertThat(snippets.httpRequest()).isHttpRequest(
(request) -> request.get("/foo?b=bravo").header("Alpha", "a").header(HttpHeaders.HOST, "localhost"));
}
@Test
public void getRequestWithPort() throws IOException {
@RenderedSnippetTest
void getRequestWithPort(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
new HttpRequestSnippet()
.document(this.operationBuilder.request("http://localhost:8080/foo").header("Alpha", "a").build());
assertThat(this.generatedSnippets.httpRequest())
.is(httpRequest(RequestMethod.GET, "/foo").header("Alpha", "a").header(HttpHeaders.HOST, "localhost:8080"));
.document(operationBuilder.request("http://localhost:8080/foo").header("Alpha", "a").build());
assertThat(snippets.httpRequest()).isHttpRequest(
(request) -> request.get("/foo").header("Alpha", "a").header(HttpHeaders.HOST, "localhost:8080"));
}
@Test
public void getRequestWithCookies() throws IOException {
new HttpRequestSnippet().document(this.operationBuilder.request("http://localhost/foo")
@RenderedSnippetTest
void getRequestWithCookies(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
new HttpRequestSnippet().document(operationBuilder.request("http://localhost/foo")
.cookie("name1", "value1")
.cookie("name2", "value2")
.build());
assertThat(this.generatedSnippets.httpRequest())
.is(httpRequest(RequestMethod.GET, "/foo").header(HttpHeaders.HOST, "localhost")
.header(HttpHeaders.COOKIE, "name1=value1; name2=value2"));
assertThat(snippets.httpRequest()).isHttpRequest((request) -> request.get("/foo")
.header(HttpHeaders.HOST, "localhost")
.header(HttpHeaders.COOKIE, "name1=value1; name2=value2"));
}
@Test
public void getRequestWithQueryString() throws IOException {
new HttpRequestSnippet().document(this.operationBuilder.request("http://localhost/foo?bar=baz").build());
assertThat(this.generatedSnippets.httpRequest())
.is(httpRequest(RequestMethod.GET, "/foo?bar=baz").header(HttpHeaders.HOST, "localhost"));
@RenderedSnippetTest
void getRequestWithQueryString(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
new HttpRequestSnippet().document(operationBuilder.request("http://localhost/foo?bar=baz").build());
assertThat(snippets.httpRequest())
.isHttpRequest((request) -> request.get("/foo?bar=baz").header(HttpHeaders.HOST, "localhost"));
}
@Test
public void getRequestWithQueryStringWithNoValue() throws IOException {
new HttpRequestSnippet().document(this.operationBuilder.request("http://localhost/foo?bar").build());
assertThat(this.generatedSnippets.httpRequest())
.is(httpRequest(RequestMethod.GET, "/foo?bar").header(HttpHeaders.HOST, "localhost"));
@RenderedSnippetTest
void getRequestWithQueryStringWithNoValue(OperationBuilder operationBuilder, AssertableSnippets snippets)
throws IOException {
new HttpRequestSnippet().document(operationBuilder.request("http://localhost/foo?bar").build());
assertThat(snippets.httpRequest())
.isHttpRequest((request) -> request.get("/foo?bar").header(HttpHeaders.HOST, "localhost"));
}
@Test
public void postRequestWithContent() throws IOException {
@RenderedSnippetTest
void postRequestWithContent(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
String content = "Hello, world";
new HttpRequestSnippet()
.document(this.operationBuilder.request("http://localhost/foo").method("POST").content(content).build());
assertThat(this.generatedSnippets.httpRequest())
.is(httpRequest(RequestMethod.POST, "/foo").header(HttpHeaders.HOST, "localhost")
.content(content)
.header(HttpHeaders.CONTENT_LENGTH, content.getBytes().length));
.document(operationBuilder.request("http://localhost/foo").method("POST").content(content).build());
assertThat(snippets.httpRequest()).isHttpRequest((request) -> request.post("/foo")
.header(HttpHeaders.HOST, "localhost")
.content(content)
.header(HttpHeaders.CONTENT_LENGTH, content.getBytes().length));
}
@Test
public void postRequestWithContentAndQueryParameters() throws IOException {
@RenderedSnippetTest
void postRequestWithContentAndQueryParameters(OperationBuilder operationBuilder, AssertableSnippets snippets)
throws IOException {
String content = "Hello, world";
new HttpRequestSnippet().document(
this.operationBuilder.request("http://localhost/foo?a=alpha").method("POST").content(content).build());
assertThat(this.generatedSnippets.httpRequest())
.is(httpRequest(RequestMethod.POST, "/foo?a=alpha").header(HttpHeaders.HOST, "localhost")
.content(content)
.header(HttpHeaders.CONTENT_LENGTH, content.getBytes().length));
new HttpRequestSnippet()
.document(operationBuilder.request("http://localhost/foo?a=alpha").method("POST").content(content).build());
assertThat(snippets.httpRequest()).isHttpRequest((request) -> request.post("/foo?a=alpha")
.header(HttpHeaders.HOST, "localhost")
.content(content)
.header(HttpHeaders.CONTENT_LENGTH, content.getBytes().length));
}
@Test
public void postRequestWithCharset() throws IOException {
@RenderedSnippetTest
void postRequestWithCharset(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
String japaneseContent = "\u30b3\u30f3\u30c6\u30f3\u30c4";
byte[] contentBytes = japaneseContent.getBytes("UTF-8");
new HttpRequestSnippet().document(this.operationBuilder.request("http://localhost/foo")
new HttpRequestSnippet().document(operationBuilder.request("http://localhost/foo")
.method("POST")
.header("Content-Type", "text/plain;charset=UTF-8")
.content(contentBytes)
.build());
assertThat(this.generatedSnippets.httpRequest())
.is(httpRequest(RequestMethod.POST, "/foo").header("Content-Type", "text/plain;charset=UTF-8")
.header(HttpHeaders.HOST, "localhost")
.header(HttpHeaders.CONTENT_LENGTH, contentBytes.length)
.content(japaneseContent));
assertThat(snippets.httpRequest()).isHttpRequest((request) -> request.post("/foo")
.header("Content-Type", "text/plain;charset=UTF-8")
.header(HttpHeaders.HOST, "localhost")
.header(HttpHeaders.CONTENT_LENGTH, contentBytes.length)
.content(japaneseContent));
}
@Test
public void putRequestWithContent() throws IOException {
@RenderedSnippetTest
void putRequestWithContent(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
String content = "Hello, world";
new HttpRequestSnippet()
.document(this.operationBuilder.request("http://localhost/foo").method("PUT").content(content).build());
assertThat(this.generatedSnippets.httpRequest())
.is(httpRequest(RequestMethod.PUT, "/foo").header(HttpHeaders.HOST, "localhost")
.content(content)
.header(HttpHeaders.CONTENT_LENGTH, content.getBytes().length));
.document(operationBuilder.request("http://localhost/foo").method("PUT").content(content).build());
assertThat(snippets.httpRequest()).isHttpRequest((request) -> request.put("/foo")
.header(HttpHeaders.HOST, "localhost")
.content(content)
.header(HttpHeaders.CONTENT_LENGTH, content.getBytes().length));
}
@Test
public void multipartPost() throws IOException {
new HttpRequestSnippet().document(this.operationBuilder.request("http://localhost/upload")
@RenderedSnippetTest
void multipartPost(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
new HttpRequestSnippet().document(operationBuilder.request("http://localhost/upload")
.method("POST")
.header(HttpHeaders.CONTENT_TYPE, MediaType.MULTIPART_FORM_DATA_VALUE)
.part("image", "<< data >>".getBytes())
.build());
String expectedContent = createPart(
String.format("Content-Disposition: " + "form-data; " + "name=image%n%n<< data >>"));
assertThat(this.generatedSnippets.httpRequest()).is(httpRequest(RequestMethod.POST, "/upload")
assertThat(snippets.httpRequest()).isHttpRequest((request) -> request.post("/upload")
.header("Content-Type", "multipart/form-data; boundary=" + BOUNDARY)
.header(HttpHeaders.HOST, "localhost")
.content(expectedContent));
}
@Test
public void multipartPut() throws IOException {
new HttpRequestSnippet().document(this.operationBuilder.request("http://localhost/upload")
@RenderedSnippetTest
void multipartPut(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
new HttpRequestSnippet().document(operationBuilder.request("http://localhost/upload")
.method("PUT")
.header(HttpHeaders.CONTENT_TYPE, MediaType.MULTIPART_FORM_DATA_VALUE)
.part("image", "<< data >>".getBytes())
.build());
String expectedContent = createPart(
String.format("Content-Disposition: " + "form-data; " + "name=image%n%n<< data >>"));
assertThat(this.generatedSnippets.httpRequest()).is(httpRequest(RequestMethod.PUT, "/upload")
assertThat(snippets.httpRequest()).isHttpRequest((request) -> request.put("/upload")
.header("Content-Type", "multipart/form-data; boundary=" + BOUNDARY)
.header(HttpHeaders.HOST, "localhost")
.content(expectedContent));
}
@Test
public void multipartPatch() throws IOException {
new HttpRequestSnippet().document(this.operationBuilder.request("http://localhost/upload")
@RenderedSnippetTest
void multipartPatch(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
new HttpRequestSnippet().document(operationBuilder.request("http://localhost/upload")
.method("PATCH")
.header(HttpHeaders.CONTENT_TYPE, MediaType.MULTIPART_FORM_DATA_VALUE)
.part("image", "<< data >>".getBytes())
.build());
String expectedContent = createPart(
String.format("Content-Disposition: " + "form-data; " + "name=image%n%n<< data >>"));
assertThat(this.generatedSnippets.httpRequest()).is(httpRequest(RequestMethod.PATCH, "/upload")
assertThat(snippets.httpRequest()).isHttpRequest((request) -> request.patch("/upload")
.header("Content-Type", "multipart/form-data; boundary=" + BOUNDARY)
.header(HttpHeaders.HOST, "localhost")
.content(expectedContent));
}
@Test
public void multipartPostWithFilename() throws IOException {
new HttpRequestSnippet().document(this.operationBuilder.request("http://localhost/upload")
@RenderedSnippetTest
void multipartPostWithFilename(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
new HttpRequestSnippet().document(operationBuilder.request("http://localhost/upload")
.method("POST")
.header(HttpHeaders.CONTENT_TYPE, MediaType.MULTIPART_FORM_DATA_VALUE)
.part("image", "<< data >>".getBytes())
@@ -204,15 +196,16 @@ public class HttpRequestSnippetTests extends AbstractSnippetTests {
.build());
String expectedContent = createPart(String
.format("Content-Disposition: " + "form-data; " + "name=image; filename=image.png%n%n<< data >>"));
assertThat(this.generatedSnippets.httpRequest()).is(httpRequest(RequestMethod.POST, "/upload")
assertThat(snippets.httpRequest()).isHttpRequest((request) -> request.post("/upload")
.header("Content-Type", "multipart/form-data; boundary=" + BOUNDARY)
.header(HttpHeaders.HOST, "localhost")
.content(expectedContent));
}
@Test
public void multipartPostWithContentType() throws IOException {
new HttpRequestSnippet().document(this.operationBuilder.request("http://localhost/upload")
@RenderedSnippetTest
void multipartPostWithContentType(OperationBuilder operationBuilder, AssertableSnippets snippets)
throws IOException {
new HttpRequestSnippet().document(operationBuilder.request("http://localhost/upload")
.method("POST")
.header(HttpHeaders.CONTENT_TYPE, MediaType.MULTIPART_FORM_DATA_VALUE)
.part("image", "<< data >>".getBytes())
@@ -220,38 +213,35 @@ public class HttpRequestSnippetTests extends AbstractSnippetTests {
.build());
String expectedContent = createPart(String
.format("Content-Disposition: form-data; name=image%nContent-Type: " + "image/png%n%n<< data >>"));
assertThat(this.generatedSnippets.httpRequest()).is(httpRequest(RequestMethod.POST, "/upload")
assertThat(snippets.httpRequest()).isHttpRequest((request) -> request.post("/upload")
.header("Content-Type", "multipart/form-data; boundary=" + BOUNDARY)
.header(HttpHeaders.HOST, "localhost")
.content(expectedContent));
}
@Test
public void getRequestWithCustomHost() throws IOException {
new HttpRequestSnippet().document(this.operationBuilder.request("http://localhost/foo")
.header(HttpHeaders.HOST, "api.example.com")
.build());
assertThat(this.generatedSnippets.httpRequest())
.is(httpRequest(RequestMethod.GET, "/foo").header(HttpHeaders.HOST, "api.example.com"));
@RenderedSnippetTest
void getRequestWithCustomHost(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
new HttpRequestSnippet().document(
operationBuilder.request("http://localhost/foo").header(HttpHeaders.HOST, "api.example.com").build());
assertThat(snippets.httpRequest())
.isHttpRequest((request) -> request.get("/foo").header(HttpHeaders.HOST, "api.example.com"));
}
@Test
public void requestWithCustomSnippetAttributes() throws IOException {
TemplateResourceResolver resolver = mock(TemplateResourceResolver.class);
given(resolver.resolveTemplateResource("http-request")).willReturn(snippetResource("http-request-with-title"));
new HttpRequestSnippet(attributes(key("title").value("Title for the request"))).document(
this.operationBuilder.attribute(TemplateEngine.class.getName(), new MustacheTemplateEngine(resolver))
.request("http://localhost/foo")
.build());
assertThat(this.generatedSnippets.httpRequest()).contains("Title for the request");
@RenderedSnippetTest
@SnippetTemplate(snippet = "http-request", template = "http-request-with-title")
void requestWithCustomSnippetAttributes(OperationBuilder operationBuilder, AssertableSnippets snippets)
throws IOException {
new HttpRequestSnippet(attributes(key("title").value("Title for the request")))
.document(operationBuilder.request("http://localhost/foo").build());
assertThat(snippets.httpRequest()).contains("Title for the request");
}
@Test
public void deleteWithQueryString() throws IOException {
@RenderedSnippetTest
void deleteWithQueryString(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
new HttpRequestSnippet()
.document(this.operationBuilder.request("http://localhost/foo?a=alpha&b=bravo").method("DELETE").build());
assertThat(this.generatedSnippets.httpRequest())
.is(httpRequest(RequestMethod.DELETE, "/foo?a=alpha&b=bravo").header("Host", "localhost"));
.document(operationBuilder.request("http://localhost/foo?a=alpha&b=bravo").method("DELETE").build());
assertThat(snippets.httpRequest())
.isHttpRequest((request) -> request.delete("/foo?a=alpha&b=bravo").header("Host", "localhost"));
}
private String createPart(String content) {

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2023 the original author or authors.
* Copyright 2014-2025 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,21 +18,16 @@ package org.springframework.restdocs.http;
import java.io.IOException;
import org.junit.Test;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.HttpStatusCode;
import org.springframework.http.MediaType;
import org.springframework.restdocs.AbstractSnippetTests;
import org.springframework.restdocs.templates.TemplateEngine;
import org.springframework.restdocs.templates.TemplateFormat;
import org.springframework.restdocs.templates.TemplateResourceResolver;
import org.springframework.restdocs.templates.mustache.MustacheTemplateEngine;
import org.springframework.restdocs.testfixtures.jupiter.AssertableSnippets;
import org.springframework.restdocs.testfixtures.jupiter.OperationBuilder;
import org.springframework.restdocs.testfixtures.jupiter.RenderedSnippetTest;
import org.springframework.restdocs.testfixtures.jupiter.SnippetTemplate;
import static org.assertj.core.api.Assertions.assertThat;
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;
@@ -42,72 +37,66 @@ import static org.springframework.restdocs.snippet.Attributes.key;
* @author Andy Wilkinson
* @author Jonathan Pearlin
*/
public class HttpResponseSnippetTests extends AbstractSnippetTests {
class HttpResponseSnippetTests {
public HttpResponseSnippetTests(String name, TemplateFormat templateFormat) {
super(name, templateFormat);
@RenderedSnippetTest
void basicResponse(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
new HttpResponseSnippet().document(operationBuilder.build());
assertThat(snippets.httpResponse()).isHttpResponse((response) -> response.ok());
}
@Test
public void basicResponse() throws IOException {
new HttpResponseSnippet().document(this.operationBuilder.build());
assertThat(this.generatedSnippets.httpResponse()).is(httpResponse(HttpStatus.OK));
@RenderedSnippetTest
void nonOkResponse(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
new HttpResponseSnippet().document(operationBuilder.response().status(HttpStatus.BAD_REQUEST).build());
assertThat(snippets.httpResponse()).isHttpResponse((response) -> response.badRequest());
}
@Test
public void nonOkResponse() throws IOException {
new HttpResponseSnippet().document(this.operationBuilder.response().status(HttpStatus.BAD_REQUEST).build());
assertThat(this.generatedSnippets.httpResponse()).is(httpResponse(HttpStatus.BAD_REQUEST));
}
@Test
public void responseWithHeaders() throws IOException {
new HttpResponseSnippet().document(this.operationBuilder.response()
@RenderedSnippetTest
void responseWithHeaders(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
new HttpResponseSnippet().document(operationBuilder.response()
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
.header("a", "alpha")
.build());
assertThat(this.generatedSnippets.httpResponse())
.is(httpResponse(HttpStatus.OK).header("Content-Type", "application/json").header("a", "alpha"));
assertThat(snippets.httpResponse()).isHttpResponse(
(response) -> response.ok().header("Content-Type", "application/json").header("a", "alpha"));
}
@Test
public void responseWithContent() throws IOException {
@RenderedSnippetTest
void responseWithContent(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
String content = "content";
new HttpResponseSnippet().document(this.operationBuilder.response().content(content).build());
assertThat(this.generatedSnippets.httpResponse()).is(httpResponse(HttpStatus.OK).content(content)
new HttpResponseSnippet().document(operationBuilder.response().content(content).build());
assertThat(snippets.httpResponse()).isHttpResponse((response) -> response.ok()
.content(content)
.header(HttpHeaders.CONTENT_LENGTH, content.getBytes().length));
}
@Test
public void responseWithCharset() throws IOException {
@RenderedSnippetTest
void responseWithCharset(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
String japaneseContent = "\u30b3\u30f3\u30c6\u30f3\u30c4";
byte[] contentBytes = japaneseContent.getBytes("UTF-8");
new HttpResponseSnippet().document(this.operationBuilder.response()
new HttpResponseSnippet().document(operationBuilder.response()
.header("Content-Type", "text/plain;charset=UTF-8")
.content(contentBytes)
.build());
assertThat(this.generatedSnippets.httpResponse())
.is(httpResponse(HttpStatus.OK).header("Content-Type", "text/plain;charset=UTF-8")
.content(japaneseContent)
.header(HttpHeaders.CONTENT_LENGTH, contentBytes.length));
assertThat(snippets.httpResponse()).isHttpResponse((response) -> response.ok()
.header("Content-Type", "text/plain;charset=UTF-8")
.content(japaneseContent)
.header(HttpHeaders.CONTENT_LENGTH, contentBytes.length));
}
@Test
public void responseWithCustomSnippetAttributes() throws IOException {
TemplateResourceResolver resolver = mock(TemplateResourceResolver.class);
given(resolver.resolveTemplateResource("http-response"))
.willReturn(snippetResource("http-response-with-title"));
new HttpResponseSnippet(attributes(key("title").value("Title for the response"))).document(
this.operationBuilder.attribute(TemplateEngine.class.getName(), new MustacheTemplateEngine(resolver))
.build());
assertThat(this.generatedSnippets.httpResponse()).contains("Title for the response");
@RenderedSnippetTest
@SnippetTemplate(snippet = "http-response", template = "http-response-with-title")
void responseWithCustomSnippetAttributes(OperationBuilder operationBuilder, AssertableSnippets snippets)
throws IOException {
new HttpResponseSnippet(attributes(key("title").value("Title for the response")))
.document(operationBuilder.build());
assertThat(snippets.httpResponse()).contains("Title for the response");
}
@Test
public void responseWithCustomStatus() throws IOException {
new HttpResponseSnippet()
.document(this.operationBuilder.response().status(HttpStatusCode.valueOf(215)).build());
assertThat(this.generatedSnippets.httpResponse()).is(httpResponse(215));
@RenderedSnippetTest
void responseWithCustomStatus(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
new HttpResponseSnippet().document(operationBuilder.response().status(HttpStatusCode.valueOf(215)).build());
assertThat(snippets.httpResponse()).isHttpResponse(((response) -> response.status(215)));
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2023 the original author or authors.
* Copyright 2014-2025 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.
@@ -20,7 +20,7 @@ import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import org.junit.Test;
import org.junit.jupiter.api.Test;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
@@ -37,18 +37,18 @@ import static org.mockito.Mockito.verify;
*
* @author Andy Wilkinson
*/
public class ContentTypeLinkExtractorTests {
class ContentTypeLinkExtractorTests {
private final OperationResponseFactory responseFactory = new OperationResponseFactory();
@Test
public void extractionFailsWithNullContentType() {
void extractionFailsWithNullContentType() {
assertThatIllegalStateException().isThrownBy(() -> new ContentTypeLinkExtractor()
.extractLinks(this.responseFactory.create(HttpStatus.OK, new HttpHeaders(), null)));
}
@Test
public void extractorCalledWithMatchingContextType() throws IOException {
void extractorCalledWithMatchingContextType() throws IOException {
Map<MediaType, LinkExtractor> extractors = new HashMap<>();
LinkExtractor extractor = mock(LinkExtractor.class);
extractors.put(MediaType.APPLICATION_JSON, extractor);
@@ -60,7 +60,7 @@ public class ContentTypeLinkExtractorTests {
}
@Test
public void extractorCalledWithCompatibleContextType() throws IOException {
void extractorCalledWithCompatibleContextType() throws IOException {
Map<MediaType, LinkExtractor> extractors = new HashMap<>();
LinkExtractor extractor = mock(LinkExtractor.class);
extractors.put(MediaType.APPLICATION_JSON, extractor);

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2023 the original author or authors.
* Copyright 2014-2025 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.
@@ -24,10 +24,9 @@ import java.util.Collections;
import java.util.List;
import java.util.Map;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedClass;
import org.junit.jupiter.params.provider.MethodSource;
import org.springframework.http.HttpStatus;
import org.springframework.restdocs.operation.OperationResponse;
@@ -39,13 +38,13 @@ import org.springframework.util.MultiValueMap;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Parameterized tests for {@link HalLinkExtractor} and {@link AtomLinkExtractor} with
* various payloads.
* Tests for {@link HalLinkExtractor} and {@link AtomLinkExtractor} with various payloads.
*
* @author Andy Wilkinson
*/
@RunWith(Parameterized.class)
public class LinkExtractorsPayloadTests {
@ParameterizedClass(name = "{1}")
@MethodSource("parameters")
class LinkExtractorsPayloadTests {
private final OperationResponseFactory responseFactory = new OperationResponseFactory();
@@ -53,25 +52,24 @@ public class LinkExtractorsPayloadTests {
private final String linkType;
@Parameters(name = "{1}")
public static Collection<Object[]> data() {
static Collection<Object[]> parameters() {
return Arrays.asList(new Object[] { new HalLinkExtractor(), "hal" },
new Object[] { new AtomLinkExtractor(), "atom" });
}
public LinkExtractorsPayloadTests(LinkExtractor linkExtractor, String linkType) {
LinkExtractorsPayloadTests(LinkExtractor linkExtractor, String linkType) {
this.linkExtractor = linkExtractor;
this.linkType = linkType;
}
@Test
public void singleLink() throws IOException {
void singleLink() throws IOException {
Map<String, List<Link>> links = this.linkExtractor.extractLinks(createResponse("single-link"));
assertLinks(Arrays.asList(new Link("alpha", "https://alpha.example.com", "Alpha")), links);
}
@Test
public void multipleLinksWithDifferentRels() throws IOException {
void multipleLinksWithDifferentRels() throws IOException {
Map<String, List<Link>> links = this.linkExtractor
.extractLinks(createResponse("multiple-links-different-rels"));
assertLinks(Arrays.asList(new Link("alpha", "https://alpha.example.com", "Alpha"),
@@ -79,20 +77,20 @@ public class LinkExtractorsPayloadTests {
}
@Test
public void multipleLinksWithSameRels() throws IOException {
void multipleLinksWithSameRels() throws IOException {
Map<String, List<Link>> links = this.linkExtractor.extractLinks(createResponse("multiple-links-same-rels"));
assertLinks(Arrays.asList(new Link("alpha", "https://alpha.example.com/one", "Alpha one"),
new Link("alpha", "https://alpha.example.com/two")), links);
}
@Test
public void noLinks() throws IOException {
void noLinks() throws IOException {
Map<String, List<Link>> links = this.linkExtractor.extractLinks(createResponse("no-links"));
assertLinks(Collections.<Link>emptyList(), links);
}
@Test
public void linksInTheWrongFormat() throws IOException {
void linksInTheWrongFormat() throws IOException {
Map<String, List<Link>> links = this.linkExtractor.extractLinks(createResponse("wrong-format"));
assertLinks(Collections.<Link>emptyList(), links);
}

View File

@@ -1,80 +0,0 @@
/*
* Copyright 2014-2023 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.hypermedia;
import java.util.Arrays;
import java.util.Collections;
import org.junit.Rule;
import org.junit.Test;
import org.springframework.restdocs.snippet.SnippetException;
import org.springframework.restdocs.templates.TemplateFormats;
import org.springframework.restdocs.testfixtures.OperationBuilder;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
/**
* Tests for failures when rendering {@link LinksSnippet} due to missing or undocumented
* links.
*
* @author Andy Wilkinson
*/
public class LinksSnippetFailureTests {
@Rule
public OperationBuilder operationBuilder = new OperationBuilder(TemplateFormats.asciidoctor());
@Test
public void undocumentedLink() {
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() {
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() {
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() {
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

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2023 the original author or authors.
* Copyright 2014-2025 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,19 +18,17 @@ package org.springframework.restdocs.hypermedia;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collections;
import org.junit.Test;
import org.springframework.restdocs.AbstractSnippetTests;
import org.springframework.restdocs.templates.TemplateEngine;
import org.springframework.restdocs.templates.TemplateFormat;
import org.springframework.restdocs.templates.TemplateFormats;
import org.springframework.restdocs.templates.TemplateResourceResolver;
import org.springframework.restdocs.templates.mustache.MustacheTemplateEngine;
import org.springframework.restdocs.snippet.SnippetException;
import org.springframework.restdocs.testfixtures.jupiter.AssertableSnippets;
import org.springframework.restdocs.testfixtures.jupiter.OperationBuilder;
import org.springframework.restdocs.testfixtures.jupiter.RenderedSnippetTest;
import org.springframework.restdocs.testfixtures.jupiter.SnippetTemplate;
import org.springframework.restdocs.testfixtures.jupiter.SnippetTest;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.springframework.restdocs.snippet.Attributes.attributes;
import static org.springframework.restdocs.snippet.Attributes.key;
@@ -39,115 +37,145 @@ import static org.springframework.restdocs.snippet.Attributes.key;
*
* @author Andy Wilkinson
*/
public class LinksSnippetTests extends AbstractSnippetTests {
class LinksSnippetTests {
public LinksSnippetTests(String name, TemplateFormat templateFormat) {
super(name, templateFormat);
}
@Test
public void ignoredLink() throws IOException {
@RenderedSnippetTest
void ignoredLink(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
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(this.operationBuilder.build());
assertThat(this.generatedSnippets.links()).is(tableWithHeader("Relation", "Description").row("`b`", "Link b"));
.document(operationBuilder.build());
assertThat(snippets.links())
.isTable((table) -> table.withHeader("Relation", "Description").row("`b`", "Link b"));
}
@Test
public void allUndocumentedLinksCanBeIgnored() throws IOException {
@RenderedSnippetTest
void allUndocumentedLinksCanBeIgnored(OperationBuilder operationBuilder, AssertableSnippets snippets)
throws IOException {
new LinksSnippet(new StubLinkExtractor().withLinks(new Link("a", "alpha"), new Link("b", "bravo")),
Arrays.asList(new LinkDescriptor("b").description("Link b")), true)
.document(this.operationBuilder.build());
assertThat(this.generatedSnippets.links()).is(tableWithHeader("Relation", "Description").row("`b`", "Link b"));
.document(operationBuilder.build());
assertThat(snippets.links())
.isTable((table) -> table.withHeader("Relation", "Description").row("`b`", "Link b"));
}
@Test
public void presentOptionalLink() throws IOException {
@RenderedSnippetTest
void presentOptionalLink(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
new LinksSnippet(new StubLinkExtractor().withLinks(new Link("foo", "blah")),
Arrays.asList(new LinkDescriptor("foo").description("bar").optional()))
.document(this.operationBuilder.build());
assertThat(this.generatedSnippets.links()).is(tableWithHeader("Relation", "Description").row("`foo`", "bar"));
.document(operationBuilder.build());
assertThat(snippets.links())
.isTable((table) -> table.withHeader("Relation", "Description").row("`foo`", "bar"));
}
@Test
public void missingOptionalLink() throws IOException {
@RenderedSnippetTest
void missingOptionalLink(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
new LinksSnippet(new StubLinkExtractor(),
Arrays.asList(new LinkDescriptor("foo").description("bar").optional()))
.document(this.operationBuilder.build());
assertThat(this.generatedSnippets.links()).is(tableWithHeader("Relation", "Description").row("`foo`", "bar"));
.document(operationBuilder.build());
assertThat(snippets.links())
.isTable((table) -> table.withHeader("Relation", "Description").row("`foo`", "bar"));
}
@Test
public void documentedLinks() throws IOException {
@RenderedSnippetTest
void documentedLinks(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
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(this.operationBuilder.build());
assertThat(this.generatedSnippets.links())
.is(tableWithHeader("Relation", "Description").row("`a`", "one").row("`b`", "two"));
.document(operationBuilder.build());
assertThat(snippets.links())
.isTable((table) -> table.withHeader("Relation", "Description").row("`a`", "one").row("`b`", "two"));
}
@Test
public void linkDescriptionFromTitleInPayload() throws IOException {
@RenderedSnippetTest
void linkDescriptionFromTitleInPayload(OperationBuilder operationBuilder, AssertableSnippets snippets)
throws IOException {
new LinksSnippet(
new StubLinkExtractor().withLinks(new Link("a", "alpha", "Link a"), new Link("b", "bravo", "Link b")),
Arrays.asList(new LinkDescriptor("a").description("one"), new LinkDescriptor("b")))
.document(this.operationBuilder.build());
assertThat(this.generatedSnippets.links())
.is(tableWithHeader("Relation", "Description").row("`a`", "one").row("`b`", "Link b"));
.document(operationBuilder.build());
assertThat(snippets.links())
.isTable((table) -> table.withHeader("Relation", "Description").row("`a`", "one").row("`b`", "Link b"));
}
@Test
public void linksWithCustomAttributes() throws IOException {
TemplateResourceResolver resolver = mock(TemplateResourceResolver.class);
given(resolver.resolveTemplateResource("links")).willReturn(snippetResource("links-with-title"));
@RenderedSnippetTest
@SnippetTemplate(snippet = "links", template = "links-with-title")
void linksWithCustomAttributes(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
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(this.operationBuilder
.attribute(TemplateEngine.class.getName(), new MustacheTemplateEngine(resolver))
.build());
assertThat(this.generatedSnippets.links()).contains("Title for the links");
.document(operationBuilder.build());
assertThat(snippets.links()).contains("Title for the links");
}
@Test
public void linksWithCustomDescriptorAttributes() throws IOException {
TemplateResourceResolver resolver = mock(TemplateResourceResolver.class);
given(resolver.resolveTemplateResource("links")).willReturn(snippetResource("links-with-extra-column"));
@RenderedSnippetTest
@SnippetTemplate(snippet = "links", template = "links-with-extra-column")
void linksWithCustomDescriptorAttributes(OperationBuilder operationBuilder, AssertableSnippets snippets)
throws IOException {
new LinksSnippet(new StubLinkExtractor().withLinks(new Link("a", "alpha"), new Link("b", "bravo")),
Arrays.asList(new LinkDescriptor("a").description("one").attributes(key("foo").value("alpha")),
new LinkDescriptor("b").description("two").attributes(key("foo").value("bravo"))))
.document(this.operationBuilder
.attribute(TemplateEngine.class.getName(), new MustacheTemplateEngine(resolver))
.build());
assertThat(this.generatedSnippets.links())
.is(tableWithHeader("Relation", "Description", "Foo").row("a", "one", "alpha").row("b", "two", "bravo"));
.document(operationBuilder.build());
assertThat(snippets.links()).isTable((table) -> table.withHeader("Relation", "Description", "Foo")
.row("a", "one", "alpha")
.row("b", "two", "bravo"));
}
@Test
public void additionalDescriptors() throws IOException {
@RenderedSnippetTest
void additionalDescriptors(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
HypermediaDocumentation
.links(new StubLinkExtractor().withLinks(new Link("a", "alpha"), new Link("b", "bravo")),
new LinkDescriptor("a").description("one"))
.and(new LinkDescriptor("b").description("two"))
.document(this.operationBuilder.build());
assertThat(this.generatedSnippets.links())
.is(tableWithHeader("Relation", "Description").row("`a`", "one").row("`b`", "two"));
.document(operationBuilder.build());
assertThat(snippets.links())
.isTable((table) -> table.withHeader("Relation", "Description").row("`a`", "one").row("`b`", "two"));
}
@Test
public void tableCellContentIsEscapedWhenNecessary() throws IOException {
@RenderedSnippetTest
void tableCellContentIsEscapedWhenNecessary(OperationBuilder operationBuilder, AssertableSnippets snippets)
throws IOException {
new LinksSnippet(new StubLinkExtractor().withLinks(new Link("Foo|Bar", "foo")),
Arrays.asList(new LinkDescriptor("Foo|Bar").description("one|two")))
.document(this.operationBuilder.build());
assertThat(this.generatedSnippets.links()).is(tableWithHeader("Relation", "Description")
.row(escapeIfNecessary("`Foo|Bar`"), escapeIfNecessary("one|two")));
.document(operationBuilder.build());
assertThat(snippets.links())
.isTable((table) -> table.withHeader("Relation", "Description").row("`Foo|Bar`", "one|two"));
}
private String escapeIfNecessary(String input) {
if (this.templateFormat.getId().equals(TemplateFormats.markdown().getId())) {
return input;
}
return input.replace("|", "\\|");
@SnippetTest
void undocumentedLink(OperationBuilder operationBuilder) {
assertThatExceptionOfType(SnippetException.class)
.isThrownBy(() -> new LinksSnippet(new StubLinkExtractor().withLinks(new Link("foo", "bar")),
Collections.<LinkDescriptor>emptyList())
.document(operationBuilder.build()))
.withMessage("Links with the following relations were not documented: [foo]");
}
@SnippetTest
void missingLink(OperationBuilder operationBuilder) {
assertThatExceptionOfType(SnippetException.class)
.isThrownBy(() -> new LinksSnippet(new StubLinkExtractor(),
Arrays.asList(new LinkDescriptor("foo").description("bar")))
.document(operationBuilder.build()))
.withMessage("Links with the following relations were not found in the response: [foo]");
}
@SnippetTest
void undocumentedLinkAndMissingLink(OperationBuilder operationBuilder) {
assertThatExceptionOfType(SnippetException.class)
.isThrownBy(() -> new LinksSnippet(new StubLinkExtractor().withLinks(new Link("a", "alpha")),
Arrays.asList(new LinkDescriptor("foo").description("bar")))
.document(operationBuilder.build()))
.withMessage("Links with the following relations were not documented: [a]. Links with the following"
+ " relations were not found in the response: [foo]");
}
@SnippetTest
void linkWithNoDescription(OperationBuilder operationBuilder) {
assertThatExceptionOfType(SnippetException.class)
.isThrownBy(() -> new LinksSnippet(new StubLinkExtractor().withLinks(new Link("foo", "bar")),
Arrays.asList(new LinkDescriptor("foo")))
.document(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

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2022 the original author or authors.
* Copyright 2014-2025 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,7 +19,7 @@ package org.springframework.restdocs.operation.preprocess;
import java.net.URI;
import java.util.Collections;
import org.junit.Test;
import org.junit.jupiter.api.Test;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
@@ -39,7 +39,7 @@ import static org.assertj.core.api.Assertions.assertThat;
* @author Andy Wilkinson
*
*/
public class ContentModifyingOperationPreprocessorTests {
class ContentModifyingOperationPreprocessorTests {
private final OperationRequestFactory requestFactory = new OperationRequestFactory();
@@ -56,7 +56,7 @@ public class ContentModifyingOperationPreprocessorTests {
});
@Test
public void modifyRequestContent() {
void modifyRequestContent() {
OperationRequest request = this.requestFactory.create(URI.create("http://localhost"), HttpMethod.GET,
"content".getBytes(), new HttpHeaders(), Collections.<OperationRequestPart>emptyList());
OperationRequest preprocessed = this.preprocessor.preprocess(request);
@@ -64,7 +64,7 @@ public class ContentModifyingOperationPreprocessorTests {
}
@Test
public void modifyResponseContent() {
void modifyResponseContent() {
OperationResponse response = this.responseFactory.create(HttpStatus.OK, new HttpHeaders(),
"content".getBytes());
OperationResponse preprocessed = this.preprocessor.preprocess(response);
@@ -72,7 +72,7 @@ public class ContentModifyingOperationPreprocessorTests {
}
@Test
public void contentLengthIsUpdated() {
void contentLengthIsUpdated() {
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.setContentLength(7);
OperationRequest request = this.requestFactory.create(URI.create("http://localhost"), HttpMethod.GET,

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2023 the original author or authors.
* Copyright 2014-2025 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,7 +18,7 @@ package org.springframework.restdocs.operation.preprocess;
import java.util.Arrays;
import org.junit.Test;
import org.junit.jupiter.api.Test;
import org.springframework.restdocs.operation.OperationRequest;
@@ -31,10 +31,10 @@ import static org.mockito.Mockito.mock;
*
* @author Andy Wilkinson
*/
public class DelegatingOperationRequestPreprocessorTests {
class DelegatingOperationRequestPreprocessorTests {
@Test
public void delegationOccurs() {
void delegationOccurs() {
OperationRequest originalRequest = mock(OperationRequest.class);
OperationPreprocessor preprocessor1 = mock(OperationPreprocessor.class);
OperationRequest preprocessedRequest1 = mock(OperationRequest.class);

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2023 the original author or authors.
* Copyright 2014-2025 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,7 +18,7 @@ package org.springframework.restdocs.operation.preprocess;
import java.util.Arrays;
import org.junit.Test;
import org.junit.jupiter.api.Test;
import org.springframework.restdocs.operation.OperationResponse;
@@ -31,10 +31,10 @@ import static org.mockito.Mockito.mock;
*
* @author Andy Wilkinson
*/
public class DelegatingOperationResponsePreprocessorTests {
class DelegatingOperationResponsePreprocessorTests {
@Test
public void delegationOccurs() {
void delegationOccurs() {
OperationResponse originalResponse = mock(OperationResponse.class);
OperationPreprocessor preprocessor1 = mock(OperationPreprocessor.class);
OperationResponse preprocessedResponse1 = mock(OperationResponse.class);

View File

@@ -22,7 +22,7 @@ import java.util.Collections;
import java.util.List;
import java.util.function.Consumer;
import org.junit.Test;
import org.junit.jupiter.api.Test;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
@@ -41,12 +41,12 @@ import static org.assertj.core.api.Assertions.entry;
* @author Jihoon Cha
* @author Andy Wilkinson
*/
public class HeadersModifyingOperationPreprocessorTests {
class HeadersModifyingOperationPreprocessorTests {
private final HeadersModifyingOperationPreprocessor preprocessor = new HeadersModifyingOperationPreprocessor();
@Test
public void addNewHeader() {
void addNewHeader() {
this.preprocessor.add("a", "alpha");
assertThat(this.preprocessor.preprocess(createRequest()).getHeaders().get("a"))
.isEqualTo(Arrays.asList("alpha"));
@@ -55,7 +55,7 @@ public class HeadersModifyingOperationPreprocessorTests {
}
@Test
public void addValueToExistingHeader() {
void addValueToExistingHeader() {
this.preprocessor.add("a", "alpha");
assertThat(this.preprocessor.preprocess(createRequest((headers) -> headers.add("a", "apple")))
.getHeaders()
@@ -66,7 +66,7 @@ public class HeadersModifyingOperationPreprocessorTests {
}
@Test
public void setNewHeader() {
void setNewHeader() {
this.preprocessor.set("a", "alpha", "avocado");
assertThat(this.preprocessor.preprocess(createRequest()).getHeaders().headerSet())
.contains(entry("a", Arrays.asList("alpha", "avocado")));
@@ -75,7 +75,7 @@ public class HeadersModifyingOperationPreprocessorTests {
}
@Test
public void setExistingHeader() {
void setExistingHeader() {
this.preprocessor.set("a", "alpha", "avocado");
assertThat(this.preprocessor.preprocess(createRequest((headers) -> headers.add("a", "apple")))
.getHeaders()
@@ -86,14 +86,14 @@ public class HeadersModifyingOperationPreprocessorTests {
}
@Test
public void removeNonExistentHeader() {
void removeNonExistentHeader() {
this.preprocessor.remove("a");
assertThat(this.preprocessor.preprocess(createRequest()).getHeaders().headerNames()).doesNotContain("a");
assertThat(this.preprocessor.preprocess(createResponse()).getHeaders().headerNames()).doesNotContain("a");
}
@Test
public void removeHeader() {
void removeHeader() {
this.preprocessor.remove("a");
assertThat(this.preprocessor.preprocess(createRequest((headers) -> headers.add("a", "apple")))
.getHeaders()
@@ -104,14 +104,14 @@ public class HeadersModifyingOperationPreprocessorTests {
}
@Test
public void removeHeaderValueForNonExistentHeader() {
void removeHeaderValueForNonExistentHeader() {
this.preprocessor.remove("a", "apple");
assertThat(this.preprocessor.preprocess(createRequest()).getHeaders().headerNames()).doesNotContain("a");
assertThat(this.preprocessor.preprocess(createResponse()).getHeaders().headerNames()).doesNotContain("a");
}
@Test
public void removeHeaderValueWithMultipleValues() {
void removeHeaderValueWithMultipleValues() {
this.preprocessor.remove("a", "apple");
assertThat(
this.preprocessor.preprocess(createRequest((headers) -> headers.addAll("a", List.of("apple", "alpha"))))
@@ -125,7 +125,7 @@ public class HeadersModifyingOperationPreprocessorTests {
}
@Test
public void removeHeaderValueWithSingleValueRemovesEntryEntirely() {
void removeHeaderValueWithSingleValueRemovesEntryEntirely() {
this.preprocessor.remove("a", "apple");
assertThat(this.preprocessor.preprocess(createRequest((headers) -> headers.add("a", "apple")))
.getHeaders()
@@ -136,7 +136,7 @@ public class HeadersModifyingOperationPreprocessorTests {
}
@Test
public void removeHeadersByNamePattern() {
void removeHeadersByNamePattern() {
Consumer<HttpHeaders> headersCustomizer = (headers) -> {
headers.add("apple", "apple");
headers.add("alpha", "alpha");

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2023 the original author or authors.
* Copyright 2014-2025 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,7 +26,7 @@ import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import org.junit.Test;
import org.junit.jupiter.api.Test;
import org.springframework.restdocs.hypermedia.Link;
@@ -38,7 +38,7 @@ import static org.assertj.core.api.Assertions.assertThat;
* @author Andy Wilkinson
*
*/
public class LinkMaskingContentModifierTests {
class LinkMaskingContentModifierTests {
private final ContentModifier contentModifier = new LinkMaskingContentModifier();
@@ -47,38 +47,38 @@ public class LinkMaskingContentModifierTests {
private final Link[] maskedLinks = new Link[] { new Link("a", "..."), new Link("b", "...") };
@Test
public void halLinksAreMasked() throws Exception {
void halLinksAreMasked() throws Exception {
assertThat(this.contentModifier.modifyContent(halPayloadWithLinks(this.links), null))
.isEqualTo(halPayloadWithLinks(this.maskedLinks));
}
@Test
public void formattedHalLinksAreMasked() throws Exception {
void formattedHalLinksAreMasked() throws Exception {
assertThat(this.contentModifier.modifyContent(formattedHalPayloadWithLinks(this.links), null))
.isEqualTo(formattedHalPayloadWithLinks(this.maskedLinks));
}
@Test
public void atomLinksAreMasked() throws Exception {
void atomLinksAreMasked() throws Exception {
assertThat(this.contentModifier.modifyContent(atomPayloadWithLinks(this.links), null))
.isEqualTo(atomPayloadWithLinks(this.maskedLinks));
}
@Test
public void formattedAtomLinksAreMasked() throws Exception {
void formattedAtomLinksAreMasked() throws Exception {
assertThat(this.contentModifier.modifyContent(formattedAtomPayloadWithLinks(this.links), null))
.isEqualTo(formattedAtomPayloadWithLinks(this.maskedLinks));
}
@Test
public void maskCanBeCustomized() throws Exception {
void maskCanBeCustomized() throws Exception {
assertThat(
new LinkMaskingContentModifier("custom").modifyContent(formattedAtomPayloadWithLinks(this.links), null))
.isEqualTo(formattedAtomPayloadWithLinks(new Link("a", "custom"), new Link("b", "custom")));
}
@Test
public void maskCanUseUtf8Characters() throws Exception {
void maskCanUseUtf8Characters() throws Exception {
String ellipsis = "\u2026";
assertThat(
new LinkMaskingContentModifier(ellipsis).modifyContent(formattedHalPayloadWithLinks(this.links), null))

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2023 the original author or authors.
* Copyright 2014-2025 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.
@@ -20,7 +20,7 @@ import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.regex.Pattern;
import org.junit.Test;
import org.junit.jupiter.api.Test;
import org.springframework.http.MediaType;
@@ -31,10 +31,10 @@ import static org.assertj.core.api.Assertions.assertThat;
*
* @author Andy Wilkinson
*/
public class PatternReplacingContentModifierTests {
class PatternReplacingContentModifierTests {
@Test
public void patternsAreReplaced() {
void patternsAreReplaced() {
Pattern pattern = Pattern.compile("[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}",
Pattern.CASE_INSENSITIVE);
PatternReplacingContentModifier contentModifier = new PatternReplacingContentModifier(pattern, "<<uuid>>");
@@ -44,7 +44,7 @@ public class PatternReplacingContentModifierTests {
}
@Test
public void contentThatDoesNotMatchIsUnchanged() {
void contentThatDoesNotMatchIsUnchanged() {
Pattern pattern = Pattern.compile("[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}",
Pattern.CASE_INSENSITIVE);
PatternReplacingContentModifier contentModifier = new PatternReplacingContentModifier(pattern, "<<uuid>>");
@@ -53,7 +53,7 @@ public class PatternReplacingContentModifierTests {
}
@Test
public void encodingIsPreservedUsingCharsetFromContentType() {
void encodingIsPreservedUsingCharsetFromContentType() {
String japaneseContent = "\u30b3\u30f3\u30c6\u30f3\u30c4";
Pattern pattern = Pattern.compile("[0-9]+");
PatternReplacingContentModifier contentModifier = new PatternReplacingContentModifier(pattern, "<<number>>");
@@ -63,7 +63,7 @@ public class PatternReplacingContentModifierTests {
}
@Test
public void encodingIsPreservedUsingFallbackCharset() {
void encodingIsPreservedUsingFallbackCharset() {
String japaneseContent = "\u30b3\u30f3\u30c6\u30f3\u30c4";
Pattern pattern = Pattern.compile("[0-9]+");
PatternReplacingContentModifier contentModifier = new PatternReplacingContentModifier(pattern, "<<number>>",

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2023 the original author or authors.
* Copyright 2014-2025 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.
@@ -20,10 +20,11 @@ import java.util.HashMap;
import java.util.Map;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.Rule;
import org.junit.Test;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.restdocs.testfixtures.OutputCaptureRule;
import org.springframework.restdocs.testfixtures.jupiter.CapturedOutput;
import org.springframework.restdocs.testfixtures.jupiter.OutputCaptureExtension;
import static org.assertj.core.api.Assertions.assertThat;
@@ -33,19 +34,17 @@ import static org.assertj.core.api.Assertions.assertThat;
* @author Andy Wilkinson
*
*/
public class PrettyPrintingContentModifierTests {
@Rule
public OutputCaptureRule outputCapture = new OutputCaptureRule();
@ExtendWith(OutputCaptureExtension.class)
class PrettyPrintingContentModifierTests {
@Test
public void prettyPrintJson() {
void prettyPrintJson() {
assertThat(new PrettyPrintingContentModifier().modifyContent("{\"a\":5}".getBytes(), null))
.isEqualTo(String.format("{%n \"a\" : 5%n}").getBytes());
}
@Test
public void prettyPrintXml() {
void prettyPrintXml() {
assertThat(new PrettyPrintingContentModifier()
.modifyContent("<one a=\"alpha\"><two b=\"bravo\"/></one>".getBytes(), null))
.isEqualTo(String
@@ -55,28 +54,28 @@ public class PrettyPrintingContentModifierTests {
}
@Test
public void empytContentIsHandledGracefully() {
void empytContentIsHandledGracefully() {
assertThat(new PrettyPrintingContentModifier().modifyContent("".getBytes(), null)).isEqualTo("".getBytes());
}
@Test
public void nonJsonAndNonXmlContentIsHandledGracefully() {
void nonJsonAndNonXmlContentIsHandledGracefully(CapturedOutput output) {
String content = "abcdefg";
assertThat(new PrettyPrintingContentModifier().modifyContent(content.getBytes(), null))
.isEqualTo(content.getBytes());
assertThat(this.outputCapture).isEmpty();
assertThat(output).isEmpty();
}
@Test
public void nonJsonContentThatInitiallyLooksLikeJsonIsHandledGracefully() {
void nonJsonContentThatInitiallyLooksLikeJsonIsHandledGracefully(CapturedOutput output) {
String content = "\"abc\",\"def\"";
assertThat(new PrettyPrintingContentModifier().modifyContent(content.getBytes(), null))
.isEqualTo(content.getBytes());
assertThat(this.outputCapture).isEmpty();
assertThat(output).isEmpty();
}
@Test
public void encodingIsPreserved() throws Exception {
void encodingIsPreserved() throws Exception {
Map<String, String> input = new HashMap<>();
input.put("japanese", "\u30b3\u30f3\u30c6\u30f3\u30c4");
ObjectMapper objectMapper = new ObjectMapper();

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2023 the original author or authors.
* Copyright 2014-2025 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,7 +21,7 @@ import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import org.junit.Test;
import org.junit.jupiter.api.Test;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
@@ -41,7 +41,7 @@ import static org.assertj.core.api.Assertions.assertThat;
*
* @author Andy Wilkinson
*/
public class UriModifyingOperationPreprocessorTests {
class UriModifyingOperationPreprocessorTests {
private final OperationRequestFactory requestFactory = new OperationRequestFactory();
@@ -50,14 +50,14 @@ public class UriModifyingOperationPreprocessorTests {
private final UriModifyingOperationPreprocessor preprocessor = new UriModifyingOperationPreprocessor();
@Test
public void requestUriSchemeCanBeModified() {
void requestUriSchemeCanBeModified() {
this.preprocessor.scheme("https");
OperationRequest processed = this.preprocessor.preprocess(createRequestWithUri("http://localhost:12345"));
assertThat(processed.getUri()).isEqualTo(URI.create("https://localhost:12345"));
}
@Test
public void requestUriHostCanBeModified() {
void requestUriHostCanBeModified() {
this.preprocessor.host("api.example.com");
OperationRequest processed = this.preprocessor.preprocess(createRequestWithUri("https://api.foo.com:12345"));
assertThat(processed.getUri()).isEqualTo(URI.create("https://api.example.com:12345"));
@@ -65,7 +65,7 @@ public class UriModifyingOperationPreprocessorTests {
}
@Test
public void requestUriPortCanBeModified() {
void requestUriPortCanBeModified() {
this.preprocessor.port(23456);
OperationRequest processed = this.preprocessor
.preprocess(createRequestWithUri("https://api.example.com:12345"));
@@ -74,7 +74,7 @@ public class UriModifyingOperationPreprocessorTests {
}
@Test
public void requestUriPortCanBeRemoved() {
void requestUriPortCanBeRemoved() {
this.preprocessor.removePort();
OperationRequest processed = this.preprocessor
.preprocess(createRequestWithUri("https://api.example.com:12345"));
@@ -83,7 +83,7 @@ public class UriModifyingOperationPreprocessorTests {
}
@Test
public void requestUriPathIsPreserved() {
void requestUriPathIsPreserved() {
this.preprocessor.removePort();
OperationRequest processed = this.preprocessor
.preprocess(createRequestWithUri("https://api.example.com:12345/foo/bar"));
@@ -91,7 +91,7 @@ public class UriModifyingOperationPreprocessorTests {
}
@Test
public void requestUriQueryIsPreserved() {
void requestUriQueryIsPreserved() {
this.preprocessor.removePort();
OperationRequest processed = this.preprocessor
.preprocess(createRequestWithUri("https://api.example.com:12345?foo=bar"));
@@ -99,7 +99,7 @@ public class UriModifyingOperationPreprocessorTests {
}
@Test
public void requestUriAnchorIsPreserved() {
void requestUriAnchorIsPreserved() {
this.preprocessor.removePort();
OperationRequest processed = this.preprocessor
.preprocess(createRequestWithUri("https://api.example.com:12345#foo"));
@@ -107,7 +107,7 @@ public class UriModifyingOperationPreprocessorTests {
}
@Test
public void requestContentUriSchemeCanBeModified() {
void requestContentUriSchemeCanBeModified() {
this.preprocessor.scheme("https");
OperationRequest processed = this.preprocessor.preprocess(createRequestWithContent(
"The uri 'https://localhost:12345' should be used. foo:bar will be unaffected"));
@@ -116,7 +116,7 @@ public class UriModifyingOperationPreprocessorTests {
}
@Test
public void requestContentUriHostCanBeModified() {
void requestContentUriHostCanBeModified() {
this.preprocessor.host("api.example.com");
OperationRequest processed = this.preprocessor.preprocess(createRequestWithContent(
"The uri 'https://localhost:12345' should be used. foo:bar will be unaffected"));
@@ -125,7 +125,7 @@ public class UriModifyingOperationPreprocessorTests {
}
@Test
public void requestContentHostOfUriWithoutPortCanBeModified() {
void requestContentHostOfUriWithoutPortCanBeModified() {
this.preprocessor.host("api.example.com");
OperationRequest processed = this.preprocessor.preprocess(
createRequestWithContent("The uri 'https://localhost' should be used. foo:bar will be unaffected"));
@@ -134,7 +134,7 @@ public class UriModifyingOperationPreprocessorTests {
}
@Test
public void requestContentUriPortCanBeAdded() {
void requestContentUriPortCanBeAdded() {
this.preprocessor.port(23456);
OperationRequest processed = this.preprocessor.preprocess(
createRequestWithContent("The uri 'http://localhost' should be used. foo:bar will be unaffected"));
@@ -143,7 +143,7 @@ public class UriModifyingOperationPreprocessorTests {
}
@Test
public void requestContentUriPortCanBeModified() {
void requestContentUriPortCanBeModified() {
this.preprocessor.port(23456);
OperationRequest processed = this.preprocessor.preprocess(createRequestWithContent(
"The uri 'http://localhost:12345' should be used. foo:bar will be unaffected"));
@@ -152,7 +152,7 @@ public class UriModifyingOperationPreprocessorTests {
}
@Test
public void requestContentUriPortCanBeRemoved() {
void requestContentUriPortCanBeRemoved() {
this.preprocessor.removePort();
OperationRequest processed = this.preprocessor.preprocess(createRequestWithContent(
"The uri 'http://localhost:12345' should be used. foo:bar will be unaffected"));
@@ -161,7 +161,7 @@ public class UriModifyingOperationPreprocessorTests {
}
@Test
public void multipleRequestContentUrisCanBeModified() {
void multipleRequestContentUrisCanBeModified() {
this.preprocessor.removePort();
OperationRequest processed = this.preprocessor.preprocess(createRequestWithContent(
"Use 'http://localhost:12345' or 'https://localhost:23456' to access the service"));
@@ -170,7 +170,7 @@ public class UriModifyingOperationPreprocessorTests {
}
@Test
public void requestContentUriPathIsPreserved() {
void requestContentUriPathIsPreserved() {
this.preprocessor.removePort();
OperationRequest processed = this.preprocessor
.preprocess(createRequestWithContent("The uri 'http://localhost:12345/foo/bar' should be used"));
@@ -178,7 +178,7 @@ public class UriModifyingOperationPreprocessorTests {
}
@Test
public void requestContentUriQueryIsPreserved() {
void requestContentUriQueryIsPreserved() {
this.preprocessor.removePort();
OperationRequest processed = this.preprocessor
.preprocess(createRequestWithContent("The uri 'http://localhost:12345?foo=bar' should be used"));
@@ -186,7 +186,7 @@ public class UriModifyingOperationPreprocessorTests {
}
@Test
public void requestContentUriAnchorIsPreserved() {
void requestContentUriAnchorIsPreserved() {
this.preprocessor.removePort();
OperationRequest processed = this.preprocessor
.preprocess(createRequestWithContent("The uri 'http://localhost:12345#foo' should be used"));
@@ -194,7 +194,7 @@ public class UriModifyingOperationPreprocessorTests {
}
@Test
public void responseContentUriSchemeCanBeModified() {
void responseContentUriSchemeCanBeModified() {
this.preprocessor.scheme("https");
OperationResponse processed = this.preprocessor
.preprocess(createResponseWithContent("The uri 'http://localhost:12345' should be used"));
@@ -202,7 +202,7 @@ public class UriModifyingOperationPreprocessorTests {
}
@Test
public void responseContentUriHostCanBeModified() {
void responseContentUriHostCanBeModified() {
this.preprocessor.host("api.example.com");
OperationResponse processed = this.preprocessor
.preprocess(createResponseWithContent("The uri 'https://localhost:12345' should be used"));
@@ -211,7 +211,7 @@ public class UriModifyingOperationPreprocessorTests {
}
@Test
public void responseContentUriPortCanBeModified() {
void responseContentUriPortCanBeModified() {
this.preprocessor.port(23456);
OperationResponse processed = this.preprocessor
.preprocess(createResponseWithContent("The uri 'http://localhost:12345' should be used"));
@@ -219,7 +219,7 @@ public class UriModifyingOperationPreprocessorTests {
}
@Test
public void responseContentUriPortCanBeRemoved() {
void responseContentUriPortCanBeRemoved() {
this.preprocessor.removePort();
OperationResponse processed = this.preprocessor
.preprocess(createResponseWithContent("The uri 'http://localhost:12345' should be used"));
@@ -227,7 +227,7 @@ public class UriModifyingOperationPreprocessorTests {
}
@Test
public void multipleResponseContentUrisCanBeModified() {
void multipleResponseContentUrisCanBeModified() {
this.preprocessor.removePort();
OperationResponse processed = this.preprocessor.preprocess(createResponseWithContent(
"Use 'http://localhost:12345' or 'https://localhost:23456' to access the service"));
@@ -236,7 +236,7 @@ public class UriModifyingOperationPreprocessorTests {
}
@Test
public void responseContentUriPathIsPreserved() {
void responseContentUriPathIsPreserved() {
this.preprocessor.removePort();
OperationResponse processed = this.preprocessor
.preprocess(createResponseWithContent("The uri 'http://localhost:12345/foo/bar' should be used"));
@@ -244,7 +244,7 @@ public class UriModifyingOperationPreprocessorTests {
}
@Test
public void responseContentUriQueryIsPreserved() {
void responseContentUriQueryIsPreserved() {
this.preprocessor.removePort();
OperationResponse processed = this.preprocessor
.preprocess(createResponseWithContent("The uri 'http://localhost:12345?foo=bar' should be used"));
@@ -252,7 +252,7 @@ public class UriModifyingOperationPreprocessorTests {
}
@Test
public void responseContentUriAnchorIsPreserved() {
void responseContentUriAnchorIsPreserved() {
this.preprocessor.removePort();
OperationResponse processed = this.preprocessor
.preprocess(createResponseWithContent("The uri 'http://localhost:12345#foo' should be used"));
@@ -260,7 +260,7 @@ public class UriModifyingOperationPreprocessorTests {
}
@Test
public void urisInRequestHeadersCanBeModified() {
void urisInRequestHeadersCanBeModified() {
OperationRequest processed = this.preprocessor.host("api.example.com")
.preprocess(createRequestWithHeader("Foo", "https://locahost:12345"));
assertThat(processed.getHeaders().getFirst("Foo")).isEqualTo("https://api.example.com:12345");
@@ -268,14 +268,14 @@ public class UriModifyingOperationPreprocessorTests {
}
@Test
public void urisInResponseHeadersCanBeModified() {
void urisInResponseHeadersCanBeModified() {
OperationResponse processed = this.preprocessor.host("api.example.com")
.preprocess(createResponseWithHeader("Foo", "https://locahost:12345"));
assertThat(processed.getHeaders().getFirst("Foo")).isEqualTo("https://api.example.com:12345");
}
@Test
public void urisInRequestPartHeadersCanBeModified() {
void urisInRequestPartHeadersCanBeModified() {
OperationRequest processed = this.preprocessor.host("api.example.com")
.preprocess(createRequestWithPartWithHeader("Foo", "https://locahost:12345"));
assertThat(processed.getParts().iterator().next().getHeaders().getFirst("Foo"))
@@ -283,7 +283,7 @@ public class UriModifyingOperationPreprocessorTests {
}
@Test
public void urisInRequestPartContentCanBeModified() {
void urisInRequestPartContentCanBeModified() {
OperationRequest processed = this.preprocessor.host("api.example.com")
.preprocess(createRequestWithPartWithContent("The uri 'https://localhost:12345' should be used"));
assertThat(new String(processed.getParts().iterator().next().getContent()))
@@ -291,7 +291,7 @@ public class UriModifyingOperationPreprocessorTests {
}
@Test
public void modifiedUriDoesNotGetDoubleEncoded() {
void modifiedUriDoesNotGetDoubleEncoded() {
this.preprocessor.scheme("https");
OperationRequest processed = this.preprocessor
.preprocess(createRequestWithUri("http://localhost:12345?foo=%7B%7D"));
@@ -300,7 +300,7 @@ public class UriModifyingOperationPreprocessorTests {
}
@Test
public void resultingRequestHasCookiesFromOriginalRequst() {
void resultingRequestHasCookiesFromOriginalRequst() {
List<RequestCookie> cookies = Arrays.asList(new RequestCookie("a", "alpha"));
OperationRequest request = this.requestFactory.create(URI.create("http://localhost:12345"), HttpMethod.GET,
new byte[0], new HttpHeaders(), Collections.<OperationRequestPart>emptyList(), cookies);

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2023 the original author or authors.
* Copyright 2014-2025 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,21 +19,13 @@ 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.templates.TemplateEngine;
import org.springframework.restdocs.templates.TemplateFormats;
import org.springframework.restdocs.templates.TemplateResourceResolver;
import org.springframework.restdocs.templates.mustache.MustacheTemplateEngine;
import org.springframework.restdocs.testfixtures.GeneratedSnippets;
import org.springframework.restdocs.testfixtures.OperationBuilder;
import org.springframework.restdocs.testfixtures.SnippetConditions;
import org.springframework.restdocs.testfixtures.jupiter.AssertableSnippets;
import org.springframework.restdocs.testfixtures.jupiter.OperationBuilder;
import org.springframework.restdocs.testfixtures.jupiter.RenderedSnippetTest;
import org.springframework.restdocs.testfixtures.jupiter.RenderedSnippetTest.Format;
import org.springframework.restdocs.testfixtures.jupiter.SnippetTemplate;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath;
/**
@@ -41,33 +33,17 @@ import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWit
*
* @author Andy Wilkinson
*/
public class AsciidoctorRequestFieldsSnippetTests {
class AsciidoctorRequestFieldsSnippetTests {
@Rule
public OperationBuilder operationBuilder = new OperationBuilder(TemplateFormats.asciidoctor());
@Rule
public GeneratedSnippets generatedSnippets = new GeneratedSnippets(TemplateFormats.asciidoctor());
@Test
public void requestFieldsWithListDescription() throws IOException {
TemplateResourceResolver resolver = mock(TemplateResourceResolver.class);
given(resolver.resolveTemplateResource("request-fields"))
.willReturn(snippetResource("request-fields-with-list-description"));
new RequestFieldsSnippet(Arrays.asList(fieldWithPath("a").description(Arrays.asList("one", "two")))).document(
this.operationBuilder.attribute(TemplateEngine.class.getName(), new MustacheTemplateEngine(resolver))
.request("http://localhost")
.content("{\"a\": \"foo\"}")
.build());
assertThat(this.generatedSnippets.requestFields())
.is(SnippetConditions.tableWithHeader(TemplateFormats.asciidoctor(), "Path", "Type", "Description")
//
.row("a", "String", String.format(" - one%n - two"))
.configuration("[cols=\"1,1,1a\"]"));
}
private FileSystemResource snippetResource(String name) {
return new FileSystemResource("src/test/resources/custom-snippet-templates/asciidoctor/" + name + ".snippet");
@RenderedSnippetTest(format = Format.ASCIIDOCTOR)
@SnippetTemplate(snippet = "request-fields", template = "request-fields-with-list-description")
void requestFieldsWithListDescription(OperationBuilder operationBuilder, AssertableSnippets snippets)
throws IOException {
new RequestFieldsSnippet(Arrays.asList(fieldWithPath("a").description(Arrays.asList("one", "two"))))
.document(operationBuilder.request("http://localhost").content("{\"a\": \"foo\"}").build());
assertThat(snippets.requestFields()).isTable((table) -> table.withHeader("Path", "Type", "Description")
.row("a", "String", String.format(" - one%n - two"))
.configuration("[cols=\"1,1,1a\"]"));
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2023 the original author or authors.
* Copyright 2014-2025 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.
@@ -25,7 +25,7 @@ import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import org.junit.Test;
import org.junit.jupiter.api.Test;
import org.springframework.http.MediaType;
@@ -38,11 +38,11 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy;
*
* @author Andy Wilkinson
*/
public class FieldPathPayloadSubsectionExtractorTests {
class FieldPathPayloadSubsectionExtractorTests {
@Test
@SuppressWarnings("unchecked")
public void extractMapSubsectionOfJsonMap() throws JsonParseException, JsonMappingException, IOException {
void extractMapSubsectionOfJsonMap() throws JsonParseException, JsonMappingException, IOException {
byte[] extractedPayload = new FieldPathPayloadSubsectionExtractor("a.b")
.extractSubsection("{\"a\":{\"b\":{\"c\":5}}}".getBytes(), MediaType.APPLICATION_JSON);
Map<String, Object> extracted = new ObjectMapper().readValue(extractedPayload, Map.class);
@@ -52,8 +52,7 @@ public class FieldPathPayloadSubsectionExtractorTests {
@Test
@SuppressWarnings("unchecked")
public void extractSingleElementArraySubsectionOfJsonMap()
throws JsonParseException, JsonMappingException, IOException {
void extractSingleElementArraySubsectionOfJsonMap() throws JsonParseException, JsonMappingException, IOException {
byte[] extractedPayload = new FieldPathPayloadSubsectionExtractor("a.[]")
.extractSubsection("{\"a\":[{\"b\":5}]}".getBytes(), MediaType.APPLICATION_JSON);
Map<String, Object> extracted = new ObjectMapper().readValue(extractedPayload, Map.class);
@@ -63,8 +62,7 @@ public class FieldPathPayloadSubsectionExtractorTests {
@Test
@SuppressWarnings("unchecked")
public void extractMultiElementArraySubsectionOfJsonMap()
throws JsonParseException, JsonMappingException, IOException {
void extractMultiElementArraySubsectionOfJsonMap() throws JsonParseException, JsonMappingException, IOException {
byte[] extractedPayload = new FieldPathPayloadSubsectionExtractor("a")
.extractSubsection("{\"a\":[{\"b\":5},{\"b\":4}]}".getBytes(), MediaType.APPLICATION_JSON);
Map<String, Object> extracted = new ObjectMapper().readValue(extractedPayload, Map.class);
@@ -74,7 +72,7 @@ public class FieldPathPayloadSubsectionExtractorTests {
@Test
@SuppressWarnings("unchecked")
public void extractMapSubsectionFromSingleElementArrayInAJsonMap()
void extractMapSubsectionFromSingleElementArrayInAJsonMap()
throws JsonParseException, JsonMappingException, IOException {
byte[] extractedPayload = new FieldPathPayloadSubsectionExtractor("a.[].b")
.extractSubsection("{\"a\":[{\"b\":{\"c\":5}}]}".getBytes(), MediaType.APPLICATION_JSON);
@@ -85,7 +83,7 @@ public class FieldPathPayloadSubsectionExtractorTests {
@Test
@SuppressWarnings("unchecked")
public void extractMapSubsectionWithCommonStructureFromMultiElementArrayInAJsonMap()
void extractMapSubsectionWithCommonStructureFromMultiElementArrayInAJsonMap()
throws JsonParseException, JsonMappingException, IOException {
byte[] extractedPayload = new FieldPathPayloadSubsectionExtractor("a.[].b")
.extractSubsection("{\"a\":[{\"b\":{\"c\":5}},{\"b\":{\"c\":6}}]}".getBytes(), MediaType.APPLICATION_JSON);
@@ -95,7 +93,7 @@ public class FieldPathPayloadSubsectionExtractorTests {
}
@Test
public void extractMapSubsectionWithVaryingStructureFromMultiElementArrayInAJsonMap() {
void extractMapSubsectionWithVaryingStructureFromMultiElementArrayInAJsonMap() {
assertThatExceptionOfType(PayloadHandlingException.class)
.isThrownBy(() -> new FieldPathPayloadSubsectionExtractor("a.[].b").extractSubsection(
"{\"a\":[{\"b\":{\"c\":5}},{\"b\":{\"c\":6, \"d\": 7}}]}".getBytes(), MediaType.APPLICATION_JSON))
@@ -103,7 +101,7 @@ public class FieldPathPayloadSubsectionExtractorTests {
}
@Test
public void extractMapSubsectionWithVaryingStructureFromInconsistentJsonMap() {
void extractMapSubsectionWithVaryingStructureFromInconsistentJsonMap() {
assertThatExceptionOfType(PayloadHandlingException.class)
.isThrownBy(() -> new FieldPathPayloadSubsectionExtractor("*.d").extractSubsection(
"{\"a\":{\"b\":1},\"c\":{\"d\":{\"e\":1,\"f\":2}}}".getBytes(), MediaType.APPLICATION_JSON))
@@ -111,7 +109,7 @@ public class FieldPathPayloadSubsectionExtractorTests {
}
@Test
public void extractMapSubsectionWithVaryingStructureFromInconsistentJsonMapWhereAllSubsectionFieldsAreOptional() {
void extractMapSubsectionWithVaryingStructureFromInconsistentJsonMapWhereAllSubsectionFieldsAreOptional() {
assertThatExceptionOfType(PayloadHandlingException.class)
.isThrownBy(() -> new FieldPathPayloadSubsectionExtractor("*.d").extractSubsection(
"{\"a\":{\"b\":1},\"c\":{\"d\":{\"e\":1,\"f\":2}}}".getBytes(), MediaType.APPLICATION_JSON,
@@ -121,7 +119,7 @@ public class FieldPathPayloadSubsectionExtractorTests {
@Test
@SuppressWarnings("unchecked")
public void extractMapSubsectionWithVaryingStructureDueToOptionalFieldsFromMultiElementArrayInAJsonMap()
void extractMapSubsectionWithVaryingStructureDueToOptionalFieldsFromMultiElementArrayInAJsonMap()
throws JsonParseException, JsonMappingException, IOException {
byte[] extractedPayload = new FieldPathPayloadSubsectionExtractor("a.[].b").extractSubsection(
"{\"a\":[{\"b\":{\"c\":5}},{\"b\":{\"c\":6, \"d\": 7}}]}".getBytes(), MediaType.APPLICATION_JSON,
@@ -133,7 +131,7 @@ public class FieldPathPayloadSubsectionExtractorTests {
@Test
@SuppressWarnings("unchecked")
public void extractMapSubsectionWithVaryingStructureDueToOptionalParentFieldsFromMultiElementArrayInAJsonMap()
void extractMapSubsectionWithVaryingStructureDueToOptionalParentFieldsFromMultiElementArrayInAJsonMap()
throws JsonParseException, JsonMappingException, IOException {
byte[] extractedPayload = new FieldPathPayloadSubsectionExtractor("a.[].b").extractSubsection(
"{\"a\":[{\"b\":{\"c\":5}},{\"b\":{\"c\":6, \"d\": { \"e\": 7}}}]}".getBytes(),
@@ -144,7 +142,7 @@ public class FieldPathPayloadSubsectionExtractorTests {
}
@Test
public void extractedSubsectionIsPrettyPrintedWhenInputIsPrettyPrinted()
void extractedSubsectionIsPrettyPrintedWhenInputIsPrettyPrinted()
throws JsonParseException, JsonMappingException, JsonProcessingException, IOException {
ObjectMapper objectMapper = new ObjectMapper().enable(SerializationFeature.INDENT_OUTPUT);
byte[] prettyPrintedPayload = objectMapper
@@ -157,7 +155,7 @@ public class FieldPathPayloadSubsectionExtractorTests {
}
@Test
public void extractedSubsectionIsNotPrettyPrintedWhenInputIsNotPrettyPrinted()
void extractedSubsectionIsNotPrettyPrintedWhenInputIsNotPrettyPrinted()
throws JsonParseException, JsonMappingException, JsonProcessingException, IOException {
ObjectMapper objectMapper = new ObjectMapper();
byte[] payload = objectMapper
@@ -169,7 +167,7 @@ public class FieldPathPayloadSubsectionExtractorTests {
}
@Test
public void extractNonExistentSubsection() {
void extractNonExistentSubsection() {
assertThatThrownBy(() -> new FieldPathPayloadSubsectionExtractor("a.c")
.extractSubsection("{\"a\":{\"b\":{\"c\":5}}}".getBytes(), MediaType.APPLICATION_JSON))
.isInstanceOf(PayloadHandlingException.class)
@@ -177,7 +175,7 @@ public class FieldPathPayloadSubsectionExtractorTests {
}
@Test
public void extractEmptyArraySubsection() {
void extractEmptyArraySubsection() {
assertThatThrownBy(() -> new FieldPathPayloadSubsectionExtractor("a")
.extractSubsection("{\"a\":[]}}".getBytes(), MediaType.APPLICATION_JSON))
.isInstanceOf(PayloadHandlingException.class)

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2023 the original author or authors.
* Copyright 2014-2025 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,7 +18,7 @@ package org.springframework.restdocs.payload;
import java.util.Collections;
import org.junit.Test;
import org.junit.jupiter.api.Test;
import org.springframework.http.MediaType;

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2023 the original author or authors.
* Copyright 2014-2025 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.
@@ -20,7 +20,7 @@ import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import org.junit.Test;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
@@ -31,10 +31,10 @@ import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
* @author Andy Wilkinson
* @author Mathias Düsterhöft
*/
public class JsonContentHandlerTests {
class JsonContentHandlerTests {
@Test
public void typeForFieldWithNullValueMustMatch() {
void typeForFieldWithNullValueMustMatch() {
FieldDescriptor descriptor = new FieldDescriptor("a").type(JsonFieldType.STRING);
assertThatExceptionOfType(FieldTypesDoNotMatchException.class)
.isThrownBy(() -> new JsonContentHandler("{\"a\": null}".getBytes(), Arrays.asList(descriptor))
@@ -42,7 +42,7 @@ public class JsonContentHandlerTests {
}
@Test
public void typeForFieldWithNotNullAndThenNullValueMustMatch() {
void typeForFieldWithNotNullAndThenNullValueMustMatch() {
FieldDescriptor descriptor = new FieldDescriptor("a[].id").type(JsonFieldType.STRING);
assertThatExceptionOfType(FieldTypesDoNotMatchException.class).isThrownBy(
() -> new JsonContentHandler("{\"a\":[{\"id\":1},{\"id\":null}]}".getBytes(), Arrays.asList(descriptor))
@@ -50,7 +50,7 @@ public class JsonContentHandlerTests {
}
@Test
public void typeForFieldWithNullAndThenNotNullValueMustMatch() {
void typeForFieldWithNullAndThenNotNullValueMustMatch() {
FieldDescriptor descriptor = new FieldDescriptor("a.[].id").type(JsonFieldType.STRING);
assertThatExceptionOfType(FieldTypesDoNotMatchException.class).isThrownBy(
() -> new JsonContentHandler("{\"a\":[{\"id\":null},{\"id\":1}]}".getBytes(), Arrays.asList(descriptor))
@@ -58,7 +58,7 @@ public class JsonContentHandlerTests {
}
@Test
public void typeForOptionalFieldWithNumberAndThenNullValueIsNumber() {
void typeForOptionalFieldWithNumberAndThenNullValueIsNumber() {
FieldDescriptor descriptor = new FieldDescriptor("a[].id").optional();
Object fieldType = new JsonContentHandler("{\"a\":[{\"id\":1},{\"id\":null}]}\"".getBytes(),
Arrays.asList(descriptor))
@@ -67,7 +67,7 @@ public class JsonContentHandlerTests {
}
@Test
public void typeForOptionalFieldWithNullAndThenNumberIsNumber() {
void typeForOptionalFieldWithNullAndThenNumberIsNumber() {
FieldDescriptor descriptor = new FieldDescriptor("a[].id").optional();
Object fieldType = new JsonContentHandler("{\"a\":[{\"id\":null},{\"id\":1}]}".getBytes(),
Arrays.asList(descriptor))
@@ -76,7 +76,7 @@ public class JsonContentHandlerTests {
}
@Test
public void typeForFieldWithNumberAndThenNullValueIsVaries() {
void typeForFieldWithNumberAndThenNullValueIsVaries() {
FieldDescriptor descriptor = new FieldDescriptor("a[].id");
Object fieldType = new JsonContentHandler("{\"a\":[{\"id\":1},{\"id\":null}]}\"".getBytes(),
Arrays.asList(descriptor))
@@ -85,7 +85,7 @@ public class JsonContentHandlerTests {
}
@Test
public void typeForFieldWithNullAndThenNumberIsVaries() {
void typeForFieldWithNullAndThenNumberIsVaries() {
FieldDescriptor descriptor = new FieldDescriptor("a[].id");
Object fieldType = new JsonContentHandler("{\"a\":[{\"id\":null},{\"id\":1}]}".getBytes(),
Arrays.asList(descriptor))
@@ -94,7 +94,7 @@ public class JsonContentHandlerTests {
}
@Test
public void typeForOptionalFieldWithNullValueCanBeProvidedExplicitly() {
void typeForOptionalFieldWithNullValueCanBeProvidedExplicitly() {
FieldDescriptor descriptor = new FieldDescriptor("a").type(JsonFieldType.STRING).optional();
Object fieldType = new JsonContentHandler("{\"a\": null}".getBytes(), Arrays.asList(descriptor))
.resolveFieldType(descriptor);
@@ -102,7 +102,7 @@ public class JsonContentHandlerTests {
}
@Test
public void typeForFieldWithSometimesPresentOptionalAncestorCanBeProvidedExplicitly() {
void typeForFieldWithSometimesPresentOptionalAncestorCanBeProvidedExplicitly() {
FieldDescriptor descriptor = new FieldDescriptor("a.[].b.c").type(JsonFieldType.NUMBER);
FieldDescriptor ancestor = new FieldDescriptor("a.[].b").optional();
Object fieldType = new JsonContentHandler("{\"a\":[ { \"d\": 4}, {\"b\":{\"c\":5}, \"d\": 4}]}".getBytes(),
@@ -112,13 +112,13 @@ public class JsonContentHandlerTests {
}
@Test
public void failsFastWithNonJsonContent() {
void failsFastWithNonJsonContent() {
assertThatExceptionOfType(PayloadHandlingException.class)
.isThrownBy(() -> new JsonContentHandler("Non-JSON content".getBytes(), Collections.emptyList()));
}
@Test
public void describedFieldThatIsNotPresentIsConsideredMissing() {
void describedFieldThatIsNotPresentIsConsideredMissing() {
List<FieldDescriptor> descriptors = Arrays.asList(new FieldDescriptor("a"), new FieldDescriptor("b"),
new FieldDescriptor("c"));
List<FieldDescriptor> missingFields = new JsonContentHandler("{\"a\": \"alpha\", \"b\":\"bravo\"}".getBytes(),
@@ -129,7 +129,7 @@ public class JsonContentHandlerTests {
}
@Test
public void describedOptionalFieldThatIsNotPresentIsNotConsideredMissing() {
void describedOptionalFieldThatIsNotPresentIsNotConsideredMissing() {
List<FieldDescriptor> descriptors = Arrays.asList(new FieldDescriptor("a"), new FieldDescriptor("b"),
new FieldDescriptor("c").optional());
List<FieldDescriptor> missingFields = new JsonContentHandler("{\"a\": \"alpha\", \"b\":\"bravo\"}".getBytes(),
@@ -139,7 +139,7 @@ public class JsonContentHandlerTests {
}
@Test
public void describedFieldThatIsNotPresentNestedBeneathOptionalFieldThatIsPresentIsConsideredMissing() {
void describedFieldThatIsNotPresentNestedBeneathOptionalFieldThatIsPresentIsConsideredMissing() {
List<FieldDescriptor> descriptors = Arrays.asList(new FieldDescriptor("a").optional(), new FieldDescriptor("b"),
new FieldDescriptor("a.c"));
List<FieldDescriptor> missingFields = new JsonContentHandler("{\"a\":\"alpha\",\"b\":\"bravo\"}".getBytes(),
@@ -150,7 +150,7 @@ public class JsonContentHandlerTests {
}
@Test
public void describedFieldThatIsNotPresentNestedBeneathOptionalFieldThatIsNotPresentIsNotConsideredMissing() {
void describedFieldThatIsNotPresentNestedBeneathOptionalFieldThatIsNotPresentIsNotConsideredMissing() {
List<FieldDescriptor> descriptors = Arrays.asList(new FieldDescriptor("a").optional(), new FieldDescriptor("b"),
new FieldDescriptor("a.c"));
List<FieldDescriptor> missingFields = new JsonContentHandler("{\"b\":\"bravo\"}".getBytes(), descriptors)
@@ -159,7 +159,7 @@ public class JsonContentHandlerTests {
}
@Test
public void describedFieldThatIsNotPresentNestedBeneathOptionalArrayThatIsEmptyIsNotConsideredMissing() {
void describedFieldThatIsNotPresentNestedBeneathOptionalArrayThatIsEmptyIsNotConsideredMissing() {
List<FieldDescriptor> descriptors = Arrays.asList(new FieldDescriptor("outer"),
new FieldDescriptor("outer[]").optional(), new FieldDescriptor("outer[].inner"));
List<FieldDescriptor> missingFields = new JsonContentHandler("{\"outer\":[]}".getBytes(), descriptors)
@@ -168,7 +168,7 @@ public class JsonContentHandlerTests {
}
@Test
public void describedSometimesPresentFieldThatIsChildOfSometimesPresentOptionalArrayIsNotConsideredMissing() {
void describedSometimesPresentFieldThatIsChildOfSometimesPresentOptionalArrayIsNotConsideredMissing() {
List<FieldDescriptor> descriptors = Arrays.asList(new FieldDescriptor("a.[].c").optional(),
new FieldDescriptor("a.[].c.d"));
List<FieldDescriptor> missingFields = new JsonContentHandler(
@@ -178,7 +178,7 @@ public class JsonContentHandlerTests {
}
@Test
public void describedMissingFieldThatIsChildOfNestedOptionalArrayThatIsEmptyIsNotConsideredMissing() {
void describedMissingFieldThatIsChildOfNestedOptionalArrayThatIsEmptyIsNotConsideredMissing() {
List<FieldDescriptor> descriptors = Arrays.asList(new FieldDescriptor("a.[].b").optional(),
new FieldDescriptor("a.[].b.[]").optional(), new FieldDescriptor("a.[].b.[].c"));
List<FieldDescriptor> missingFields = new JsonContentHandler("{\"a\":[{\"b\":[]}]}".getBytes(), descriptors)
@@ -187,7 +187,7 @@ public class JsonContentHandlerTests {
}
@Test
public void describedMissingFieldThatIsChildOfNestedOptionalArrayThatContainsAnObjectIsConsideredMissing() {
void describedMissingFieldThatIsChildOfNestedOptionalArrayThatContainsAnObjectIsConsideredMissing() {
List<FieldDescriptor> descriptors = Arrays.asList(new FieldDescriptor("a.[].b").optional(),
new FieldDescriptor("a.[].b.[]").optional(), new FieldDescriptor("a.[].b.[].c"));
List<FieldDescriptor> missingFields = new JsonContentHandler("{\"a\":[{\"b\":[{}]}]}".getBytes(), descriptors)
@@ -197,7 +197,7 @@ public class JsonContentHandlerTests {
}
@Test
public void describedMissingFieldThatIsChildOfOptionalObjectThatIsNullIsNotConsideredMissing() {
void describedMissingFieldThatIsChildOfOptionalObjectThatIsNullIsNotConsideredMissing() {
List<FieldDescriptor> descriptors = Arrays.asList(new FieldDescriptor("a").optional(),
new FieldDescriptor("a.b"));
List<FieldDescriptor> missingFields = new JsonContentHandler("{\"a\":null}".getBytes(), descriptors)

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2019 the original author or authors.
* Copyright 2014-2025 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,7 +16,7 @@
package org.springframework.restdocs.payload;
import org.junit.Test;
import org.junit.jupiter.api.Test;
import org.springframework.restdocs.payload.JsonFieldPath.PathType;
@@ -28,131 +28,131 @@ import static org.assertj.core.api.Assertions.assertThat;
* @author Andy Wilkinson
* @author Jeremy Rickard
*/
public class JsonFieldPathTests {
class JsonFieldPathTests {
@Test
public void pathTypeOfSingleFieldIsSingle() {
void pathTypeOfSingleFieldIsSingle() {
JsonFieldPath path = JsonFieldPath.compile("a");
assertThat(path.getType()).isEqualTo(PathType.SINGLE);
}
@Test
public void pathTypeOfSingleNestedFieldIsSingle() {
void pathTypeOfSingleNestedFieldIsSingle() {
JsonFieldPath path = JsonFieldPath.compile("a.b");
assertThat(path.getType()).isEqualTo(PathType.SINGLE);
}
@Test
public void pathTypeOfTopLevelArrayIsSingle() {
void pathTypeOfTopLevelArrayIsSingle() {
JsonFieldPath path = JsonFieldPath.compile("[]");
assertThat(path.getType()).isEqualTo(PathType.SINGLE);
}
@Test
public void pathTypeOfFieldBeneathTopLevelArrayIsMulti() {
void pathTypeOfFieldBeneathTopLevelArrayIsMulti() {
JsonFieldPath path = JsonFieldPath.compile("[]a");
assertThat(path.getType()).isEqualTo(PathType.MULTI);
}
@Test
public void pathTypeOfSingleNestedArrayIsSingle() {
void pathTypeOfSingleNestedArrayIsSingle() {
JsonFieldPath path = JsonFieldPath.compile("a[]");
assertThat(path.getType()).isEqualTo(PathType.SINGLE);
}
@Test
public void pathTypeOfArrayBeneathNestedFieldsIsSingle() {
void pathTypeOfArrayBeneathNestedFieldsIsSingle() {
JsonFieldPath path = JsonFieldPath.compile("a.b[]");
assertThat(path.getType()).isEqualTo(PathType.SINGLE);
}
@Test
public void pathTypeOfArrayOfArraysIsMulti() {
void pathTypeOfArrayOfArraysIsMulti() {
JsonFieldPath path = JsonFieldPath.compile("a[][]");
assertThat(path.getType()).isEqualTo(PathType.MULTI);
}
@Test
public void pathTypeOfFieldBeneathAnArrayIsMulti() {
void pathTypeOfFieldBeneathAnArrayIsMulti() {
JsonFieldPath path = JsonFieldPath.compile("a[].b");
assertThat(path.getType()).isEqualTo(PathType.MULTI);
}
@Test
public void pathTypeOfFieldBeneathTopLevelWildcardIsMulti() {
void pathTypeOfFieldBeneathTopLevelWildcardIsMulti() {
JsonFieldPath path = JsonFieldPath.compile("*.a");
assertThat(path.getType()).isEqualTo(PathType.MULTI);
}
@Test
public void pathTypeOfFieldBeneathNestedWildcardIsMulti() {
void pathTypeOfFieldBeneathNestedWildcardIsMulti() {
JsonFieldPath path = JsonFieldPath.compile("a.*.b");
assertThat(path.getType()).isEqualTo(PathType.MULTI);
}
@Test
public void pathTypeOfLeafWidlcardIsMulti() {
void pathTypeOfLeafWidlcardIsMulti() {
JsonFieldPath path = JsonFieldPath.compile("a.*");
assertThat(path.getType()).isEqualTo(PathType.MULTI);
}
@Test
public void compilationOfSingleElementPath() {
void compilationOfSingleElementPath() {
assertThat(JsonFieldPath.compile("a").getSegments()).containsExactly("a");
}
@Test
public void compilationOfMultipleElementPath() {
void compilationOfMultipleElementPath() {
assertThat(JsonFieldPath.compile("a.b.c").getSegments()).containsExactly("a", "b", "c");
}
@Test
public void compilationOfPathWithArraysWithNoDotSeparators() {
void compilationOfPathWithArraysWithNoDotSeparators() {
assertThat(JsonFieldPath.compile("a[]b[]c").getSegments()).containsExactly("a", "[]", "b", "[]", "c");
}
@Test
public void compilationOfPathWithArraysWithPreAndPostDotSeparators() {
void compilationOfPathWithArraysWithPreAndPostDotSeparators() {
assertThat(JsonFieldPath.compile("a.[].b.[].c").getSegments()).containsExactly("a", "[]", "b", "[]", "c");
}
@Test
public void compilationOfPathWithArraysWithPreDotSeparators() {
void compilationOfPathWithArraysWithPreDotSeparators() {
assertThat(JsonFieldPath.compile("a.[]b.[]c").getSegments()).containsExactly("a", "[]", "b", "[]", "c");
}
@Test
public void compilationOfPathWithArraysWithPostDotSeparators() {
void compilationOfPathWithArraysWithPostDotSeparators() {
assertThat(JsonFieldPath.compile("a[].b[].c").getSegments()).containsExactly("a", "[]", "b", "[]", "c");
}
@Test
public void compilationOfPathStartingWithAnArray() {
void compilationOfPathStartingWithAnArray() {
assertThat(JsonFieldPath.compile("[]a.b.c").getSegments()).containsExactly("[]", "a", "b", "c");
}
@Test
public void compilationOfMultipleElementPathWithBrackets() {
void compilationOfMultipleElementPathWithBrackets() {
assertThat(JsonFieldPath.compile("['a']['b']['c']").getSegments()).containsExactly("a", "b", "c");
}
@Test
public void compilationOfMultipleElementPathWithAndWithoutBrackets() {
void compilationOfMultipleElementPathWithAndWithoutBrackets() {
assertThat(JsonFieldPath.compile("['a'][].b['c']").getSegments()).containsExactly("a", "[]", "b", "c");
}
@Test
public void compilationOfMultipleElementPathWithAndWithoutBracketsAndEmbeddedDots() {
void compilationOfMultipleElementPathWithAndWithoutBracketsAndEmbeddedDots() {
assertThat(JsonFieldPath.compile("['a.key'][].b['c']").getSegments()).containsExactly("a.key", "[]", "b", "c");
}
@Test
public void compilationOfPathWithAWildcard() {
void compilationOfPathWithAWildcard() {
assertThat(JsonFieldPath.compile("a.b.*.c").getSegments()).containsExactly("a", "b", "*", "c");
}
@Test
public void compilationOfPathWithAWildcardInBrackets() {
void compilationOfPathWithAWildcardInBrackets() {
assertThat(JsonFieldPath.compile("a.b.['*'].c").getSegments()).containsExactly("a", "b", "*", "c");
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2023 the original author or authors.
* Copyright 2014-2025 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.
@@ -20,7 +20,7 @@ import java.io.IOException;
import java.util.Arrays;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.Test;
import org.junit.jupiter.api.Test;
import org.springframework.restdocs.payload.JsonFieldProcessor.ExtractedField;
@@ -31,10 +31,10 @@ import static org.assertj.core.api.Assertions.assertThat;
*
* @author Andy Wilkinson
*/
public class JsonFieldPathsTests {
class JsonFieldPathsTests {
@Test
public void noUncommonPathsForSingleItem() {
void noUncommonPathsForSingleItem() {
assertThat(
JsonFieldPaths.from(Arrays.asList(json("{\"a\": 1, \"b\": [ { \"c\": 2}, {\"c\": 3}, {\"c\": null}]}")))
.getUncommon())
@@ -42,20 +42,20 @@ public class JsonFieldPathsTests {
}
@Test
public void noUncommonPathsForMultipleIdenticalItems() {
void noUncommonPathsForMultipleIdenticalItems() {
Object item = json("{\"a\": 1, \"b\": [ { \"c\": 2}, {\"c\": 3} ]}");
assertThat(JsonFieldPaths.from(Arrays.asList(item, item)).getUncommon()).isEmpty();
}
@Test
public void noUncommonPathsForMultipleMatchingItemsWithDifferentScalarValues() {
void noUncommonPathsForMultipleMatchingItemsWithDifferentScalarValues() {
assertThat(JsonFieldPaths.from(Arrays.asList(json("{\"a\": 1, \"b\": [ { \"c\": 2}, {\"c\": 3} ]}"),
json("{\"a\": 4, \"b\": [ { \"c\": 5}, {\"c\": 6} ]}")))
.getUncommon()).isEmpty();
}
@Test
public void missingEntryInMapIsIdentifiedAsUncommon() {
void missingEntryInMapIsIdentifiedAsUncommon() {
assertThat(
JsonFieldPaths.from(Arrays.asList(json("{\"a\": 1}"), json("{\"a\": 1}"), json("{\"a\": 1, \"b\": 2}")))
.getUncommon())
@@ -63,21 +63,21 @@ public class JsonFieldPathsTests {
}
@Test
public void missingEntryInNestedMapIsIdentifiedAsUncommon() {
void missingEntryInNestedMapIsIdentifiedAsUncommon() {
assertThat(JsonFieldPaths.from(Arrays.asList(json("{\"a\": 1, \"b\": {\"c\": 1}}"),
json("{\"a\": 1, \"b\": {\"c\": 1}}"), json("{\"a\": 1, \"b\": {\"c\": 1, \"d\": 2}}")))
.getUncommon()).containsExactly("b.d");
}
@Test
public void missingEntriesInNestedMapAreIdentifiedAsUncommon() {
void missingEntriesInNestedMapAreIdentifiedAsUncommon() {
assertThat(JsonFieldPaths.from(Arrays.asList(json("{\"a\": 1, \"b\": {\"c\": 1}}"),
json("{\"a\": 1, \"b\": {\"c\": 1}}"), json("{\"a\": 1, \"b\": {\"d\": 2}}")))
.getUncommon()).containsExactly("b.c", "b.d");
}
@Test
public void absentItemFromFieldExtractionCausesAllPresentFieldsToBeIdentifiedAsUncommon() {
void absentItemFromFieldExtractionCausesAllPresentFieldsToBeIdentifiedAsUncommon() {
assertThat(JsonFieldPaths
.from(Arrays.asList(ExtractedField.ABSENT, json("{\"a\": 1, \"b\": {\"c\": 1}}"),
json("{\"a\": 1, \"b\": {\"c\": 1}}"), json("{\"a\": 1, \"b\": {\"d\": 2}}")))
@@ -85,14 +85,14 @@ public class JsonFieldPathsTests {
}
@Test
public void missingEntryBeneathArrayIsIdentifiedAsUncommon() {
void missingEntryBeneathArrayIsIdentifiedAsUncommon() {
assertThat(JsonFieldPaths
.from(Arrays.asList(json("[{\"b\": 1}]"), json("[{\"b\": 1}]"), json("[{\"b\": 1, \"c\": 2}]")))
.getUncommon()).containsExactly("[].c");
}
@Test
public void missingEntryBeneathNestedArrayIsIdentifiedAsUncommon() {
void missingEntryBeneathNestedArrayIsIdentifiedAsUncommon() {
assertThat(JsonFieldPaths.from(Arrays.asList(json("{\"a\": [{\"b\": 1}]}"), json("{\"a\": [{\"b\": 1}]}"),
json("{\"a\": [{\"b\": 1, \"c\": 2}]}")))
.getUncommon()).containsExactly("a.[].c");

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2023 the original author or authors.
* Copyright 2014-2025 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,7 +26,7 @@ import java.util.List;
import java.util.Map;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.Test;
import org.junit.jupiter.api.Test;
import org.springframework.restdocs.payload.JsonFieldProcessor.ExtractedField;
@@ -37,19 +37,19 @@ import static org.assertj.core.api.Assertions.assertThat;
*
* @author Andy Wilkinson
*/
public class JsonFieldProcessorTests {
class JsonFieldProcessorTests {
private final JsonFieldProcessor fieldProcessor = new JsonFieldProcessor();
@Test
public void extractTopLevelMapEntry() {
void extractTopLevelMapEntry() {
Map<String, Object> payload = new HashMap<>();
payload.put("a", "alpha");
assertThat(this.fieldProcessor.extract("a", payload).getValue()).isEqualTo("alpha");
}
@Test
public void extractNestedMapEntry() {
void extractNestedMapEntry() {
Map<String, Object> payload = new HashMap<>();
Map<String, Object> alpha = new HashMap<>();
payload.put("a", alpha);
@@ -58,7 +58,7 @@ public class JsonFieldProcessorTests {
}
@Test
public void extractTopLevelArray() {
void extractTopLevelArray() {
List<Map<String, Object>> payload = new ArrayList<>();
Map<String, Object> bravo = new HashMap<>();
bravo.put("b", "bravo");
@@ -68,7 +68,7 @@ public class JsonFieldProcessorTests {
}
@Test
public void extractArray() {
void extractArray() {
Map<String, Object> payload = new HashMap<>();
Map<String, Object> bravo = new HashMap<>();
bravo.put("b", "bravo");
@@ -78,7 +78,7 @@ public class JsonFieldProcessorTests {
}
@Test
public void extractArrayContents() {
void extractArrayContents() {
Map<String, Object> payload = new HashMap<>();
Map<String, Object> bravo = new HashMap<>();
bravo.put("b", "bravo");
@@ -88,7 +88,7 @@ public class JsonFieldProcessorTests {
}
@Test
public void extractFromItemsInArray() {
void extractFromItemsInArray() {
Map<String, Object> payload = new HashMap<>();
Map<String, Object> entry = new HashMap<>();
entry.put("b", "bravo");
@@ -98,7 +98,7 @@ public class JsonFieldProcessorTests {
}
@Test
public void extractOccasionallyAbsentFieldFromItemsInArray() {
void extractOccasionallyAbsentFieldFromItemsInArray() {
Map<String, Object> payload = new HashMap<>();
Map<String, Object> entry = new HashMap<>();
entry.put("b", "bravo");
@@ -109,7 +109,7 @@ public class JsonFieldProcessorTests {
}
@Test
public void extractOccasionallyNullFieldFromItemsInArray() {
void extractOccasionallyNullFieldFromItemsInArray() {
Map<String, Object> payload = new HashMap<>();
Map<String, Object> nonNullField = new HashMap<>();
nonNullField.put("b", "bravo");
@@ -121,7 +121,7 @@ public class JsonFieldProcessorTests {
}
@Test
public void extractNestedArray() {
void extractNestedArray() {
Map<String, Object> payload = new HashMap<>();
Map<String, String> entry1 = createEntry("id:1");
Map<String, String> entry2 = createEntry("id:2");
@@ -133,7 +133,7 @@ public class JsonFieldProcessorTests {
}
@Test
public void extractFromItemsInNestedArray() {
void extractFromItemsInNestedArray() {
Map<String, Object> payload = new HashMap<>();
Map<String, String> entry1 = createEntry("id:1");
Map<String, String> entry2 = createEntry("id:2");
@@ -144,7 +144,7 @@ public class JsonFieldProcessorTests {
}
@Test
public void extractArraysFromItemsInNestedArray() {
void extractArraysFromItemsInNestedArray() {
Map<String, Object> payload = new HashMap<>();
Map<String, Object> entry1 = createEntry("ids", Arrays.asList(1, 2));
Map<String, Object> entry2 = createEntry("ids", Arrays.asList(3));
@@ -156,27 +156,27 @@ public class JsonFieldProcessorTests {
}
@Test
public void nonExistentTopLevelField() {
void nonExistentTopLevelField() {
assertThat(this.fieldProcessor.extract("a", Collections.emptyMap()).getValue())
.isEqualTo(ExtractedField.ABSENT);
}
@Test
public void nonExistentNestedField() {
void nonExistentNestedField() {
HashMap<String, Object> payload = new HashMap<>();
payload.put("a", new HashMap<String, Object>());
payload.put("a", new HashMap<>());
assertThat(this.fieldProcessor.extract("a.b", payload).getValue()).isEqualTo(ExtractedField.ABSENT);
}
@Test
public void nonExistentNestedFieldWhenParentIsNotAMap() {
void nonExistentNestedFieldWhenParentIsNotAMap() {
HashMap<String, Object> payload = new HashMap<>();
payload.put("a", 5);
assertThat(this.fieldProcessor.extract("a.b", payload).getValue()).isEqualTo(ExtractedField.ABSENT);
}
@Test
public void nonExistentFieldWhenParentIsAnArray() {
void nonExistentFieldWhenParentIsAnArray() {
HashMap<String, Object> payload = new HashMap<>();
HashMap<String, Object> alpha = new HashMap<>();
alpha.put("b", Arrays.asList(new HashMap<String, Object>()));
@@ -185,20 +185,20 @@ public class JsonFieldProcessorTests {
}
@Test
public void nonExistentArrayField() {
void nonExistentArrayField() {
HashMap<String, Object> payload = new HashMap<>();
assertThat(this.fieldProcessor.extract("a[]", payload).getValue()).isEqualTo(ExtractedField.ABSENT);
}
@Test
public void nonExistentArrayFieldAsTypeDoesNotMatch() {
void nonExistentArrayFieldAsTypeDoesNotMatch() {
HashMap<String, Object> payload = new HashMap<>();
payload.put("a", 5);
assertThat(this.fieldProcessor.extract("a[]", payload).getValue()).isEqualTo(ExtractedField.ABSENT);
}
@Test
public void nonExistentFieldBeneathAnArray() {
void nonExistentFieldBeneathAnArray() {
HashMap<String, Object> payload = new HashMap<>();
HashMap<String, Object> alpha = new HashMap<>();
alpha.put("b", Arrays.asList(new HashMap<String, Object>()));
@@ -208,7 +208,7 @@ public class JsonFieldProcessorTests {
}
@Test
public void removeTopLevelMapEntry() {
void removeTopLevelMapEntry() {
Map<String, Object> payload = new HashMap<>();
payload.put("a", "alpha");
this.fieldProcessor.remove("a", payload);
@@ -216,7 +216,7 @@ public class JsonFieldProcessorTests {
}
@Test
public void mapWithEntriesIsNotRemovedWhenNotAlsoRemovingDescendants() {
void mapWithEntriesIsNotRemovedWhenNotAlsoRemovingDescendants() {
Map<String, Object> payload = new HashMap<>();
Map<String, Object> alpha = new HashMap<>();
payload.put("a", alpha);
@@ -226,7 +226,7 @@ public class JsonFieldProcessorTests {
}
@Test
public void removeSubsectionRemovesMapWithEntries() {
void removeSubsectionRemovesMapWithEntries() {
Map<String, Object> payload = new HashMap<>();
Map<String, Object> alpha = new HashMap<>();
payload.put("a", alpha);
@@ -236,7 +236,7 @@ public class JsonFieldProcessorTests {
}
@Test
public void removeNestedMapEntry() {
void removeNestedMapEntry() {
Map<String, Object> payload = new HashMap<>();
Map<String, Object> alpha = new HashMap<>();
payload.put("a", alpha);
@@ -247,7 +247,7 @@ public class JsonFieldProcessorTests {
@SuppressWarnings("unchecked")
@Test
public void removeItemsInArray() throws IOException {
void removeItemsInArray() throws IOException {
Map<String, Object> payload = new ObjectMapper().readValue("{\"a\": [{\"b\":\"bravo\"},{\"b\":\"bravo\"}]}",
Map.class);
this.fieldProcessor.remove("a[].b", payload);
@@ -256,7 +256,7 @@ public class JsonFieldProcessorTests {
@SuppressWarnings("unchecked")
@Test
public void removeItemsInNestedArray() throws IOException {
void removeItemsInNestedArray() throws IOException {
Map<String, Object> payload = new ObjectMapper().readValue("{\"a\": [[{\"id\":1},{\"id\":2}], [{\"id\":3}]]}",
Map.class);
this.fieldProcessor.remove("a[][].id", payload);
@@ -265,7 +265,7 @@ public class JsonFieldProcessorTests {
@SuppressWarnings("unchecked")
@Test
public void removeDoesNotRemoveArrayWithMapEntries() throws IOException {
void removeDoesNotRemoveArrayWithMapEntries() throws IOException {
Map<String, Object> payload = new ObjectMapper().readValue("{\"a\": [{\"b\":\"bravo\"},{\"b\":\"bravo\"}]}",
Map.class);
this.fieldProcessor.remove("a[]", payload);
@@ -274,7 +274,7 @@ public class JsonFieldProcessorTests {
@SuppressWarnings("unchecked")
@Test
public void removeDoesNotRemoveArrayWithListEntries() throws IOException {
void removeDoesNotRemoveArrayWithListEntries() throws IOException {
Map<String, Object> payload = new ObjectMapper().readValue("{\"a\": [[2],[3]]}", Map.class);
this.fieldProcessor.remove("a[]", payload);
assertThat(payload.size()).isEqualTo(1);
@@ -282,7 +282,7 @@ public class JsonFieldProcessorTests {
@SuppressWarnings("unchecked")
@Test
public void removeRemovesArrayWithOnlyScalarEntries() throws IOException {
void removeRemovesArrayWithOnlyScalarEntries() throws IOException {
Map<String, Object> payload = new ObjectMapper().readValue("{\"a\": [\"bravo\", \"charlie\"]}", Map.class);
this.fieldProcessor.remove("a", payload);
assertThat(payload.size()).isEqualTo(0);
@@ -290,7 +290,7 @@ public class JsonFieldProcessorTests {
@SuppressWarnings("unchecked")
@Test
public void removeSubsectionRemovesArrayWithMapEntries() throws IOException {
void removeSubsectionRemovesArrayWithMapEntries() throws IOException {
Map<String, Object> payload = new ObjectMapper().readValue("{\"a\": [{\"b\":\"bravo\"},{\"b\":\"bravo\"}]}",
Map.class);
this.fieldProcessor.removeSubsection("a[]", payload);
@@ -299,14 +299,14 @@ public class JsonFieldProcessorTests {
@SuppressWarnings("unchecked")
@Test
public void removeSubsectionRemovesArrayWithListEntries() throws IOException {
void removeSubsectionRemovesArrayWithListEntries() throws IOException {
Map<String, Object> payload = new ObjectMapper().readValue("{\"a\": [[2],[3]]}", Map.class);
this.fieldProcessor.removeSubsection("a[]", payload);
assertThat(payload.size()).isEqualTo(0);
}
@Test
public void extractNestedEntryWithDotInKeys() {
void extractNestedEntryWithDotInKeys() {
Map<String, Object> payload = new HashMap<>();
Map<String, Object> alpha = new HashMap<>();
payload.put("a.key", alpha);
@@ -316,7 +316,7 @@ public class JsonFieldProcessorTests {
@SuppressWarnings("unchecked")
@Test
public void extractNestedEntriesUsingTopLevelWildcard() {
void extractNestedEntriesUsingTopLevelWildcard() {
Map<String, Object> payload = new LinkedHashMap<>();
Map<String, Object> alpha = new LinkedHashMap<>();
payload.put("a", alpha);
@@ -330,7 +330,7 @@ public class JsonFieldProcessorTests {
@SuppressWarnings("unchecked")
@Test
public void extractNestedEntriesUsingMidLevelWildcard() {
void extractNestedEntriesUsingMidLevelWildcard() {
Map<String, Object> payload = new LinkedHashMap<>();
Map<String, Object> alpha = new LinkedHashMap<>();
payload.put("a", alpha);
@@ -344,7 +344,7 @@ public class JsonFieldProcessorTests {
@SuppressWarnings("unchecked")
@Test
public void extractUsingLeafWildcardMatchingSingleItem() {
void extractUsingLeafWildcardMatchingSingleItem() {
Map<String, Object> payload = new HashMap<>();
Map<String, Object> alpha = new HashMap<>();
payload.put("a", alpha);
@@ -357,7 +357,7 @@ public class JsonFieldProcessorTests {
@SuppressWarnings("unchecked")
@Test
public void extractUsingLeafWildcardMatchingMultipleItems() {
void extractUsingLeafWildcardMatchingMultipleItems() {
Map<String, Object> payload = new HashMap<>();
Map<String, Object> alpha = new HashMap<>();
payload.put("a", alpha);
@@ -368,7 +368,7 @@ public class JsonFieldProcessorTests {
}
@Test
public void removeUsingLeafWildcard() {
void removeUsingLeafWildcard() {
Map<String, Object> payload = new HashMap<>();
Map<String, Object> alpha = new HashMap<>();
payload.put("a", alpha);
@@ -379,7 +379,7 @@ public class JsonFieldProcessorTests {
}
@Test
public void removeUsingTopLevelWildcard() {
void removeUsingTopLevelWildcard() {
Map<String, Object> payload = new HashMap<>();
Map<String, Object> alpha = new HashMap<>();
payload.put("a", alpha);
@@ -390,7 +390,7 @@ public class JsonFieldProcessorTests {
}
@Test
public void removeUsingMidLevelWildcard() {
void removeUsingMidLevelWildcard() {
Map<String, Object> payload = new LinkedHashMap<>();
Map<String, Object> alpha = new LinkedHashMap<>();
payload.put("a", alpha);
@@ -407,28 +407,28 @@ public class JsonFieldProcessorTests {
}
@Test
public void hasFieldIsTrueForNonNullFieldInMap() {
void hasFieldIsTrueForNonNullFieldInMap() {
Map<String, Object> payload = new HashMap<>();
payload.put("a", "alpha");
assertThat(this.fieldProcessor.hasField("a", payload)).isTrue();
}
@Test
public void hasFieldIsTrueForNullFieldInMap() {
void hasFieldIsTrueForNullFieldInMap() {
Map<String, Object> payload = new HashMap<>();
payload.put("a", null);
assertThat(this.fieldProcessor.hasField("a", payload)).isTrue();
}
@Test
public void hasFieldIsFalseForAbsentFieldInMap() {
void hasFieldIsFalseForAbsentFieldInMap() {
Map<String, Object> payload = new HashMap<>();
payload.put("a", null);
assertThat(this.fieldProcessor.hasField("b", payload)).isFalse();
}
@Test
public void hasFieldIsTrueForNeverNullFieldBeneathArray() {
void hasFieldIsTrueForNeverNullFieldBeneathArray() {
Map<String, Object> payload = new HashMap<>();
Map<String, Object> nested = new HashMap<>();
nested.put("b", "bravo");
@@ -437,7 +437,7 @@ public class JsonFieldProcessorTests {
}
@Test
public void hasFieldIsTrueForAlwaysNullFieldBeneathArray() {
void hasFieldIsTrueForAlwaysNullFieldBeneathArray() {
Map<String, Object> payload = new HashMap<>();
Map<String, Object> nested = new HashMap<>();
nested.put("b", null);
@@ -446,7 +446,7 @@ public class JsonFieldProcessorTests {
}
@Test
public void hasFieldIsFalseForAlwaysAbsentFieldBeneathArray() {
void hasFieldIsFalseForAlwaysAbsentFieldBeneathArray() {
Map<String, Object> payload = new HashMap<>();
Map<String, Object> nested = new HashMap<>();
nested.put("b", "bravo");
@@ -455,7 +455,7 @@ public class JsonFieldProcessorTests {
}
@Test
public void hasFieldIsFalseForOccasionallyAbsentFieldBeneathArray() {
void hasFieldIsFalseForOccasionallyAbsentFieldBeneathArray() {
Map<String, Object> payload = new HashMap<>();
Map<String, Object> nested = new HashMap<>();
nested.put("b", "bravo");
@@ -464,7 +464,7 @@ public class JsonFieldProcessorTests {
}
@Test
public void hasFieldIsFalseForOccasionallyNullFieldBeneathArray() {
void hasFieldIsFalseForOccasionallyNullFieldBeneathArray() {
Map<String, Object> payload = new HashMap<>();
Map<String, Object> fieldPresent = new HashMap<>();
fieldPresent.put("b", "bravo");

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2023 the original author or authors.
* Copyright 2014-2025 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,7 +19,7 @@ package org.springframework.restdocs.payload;
import java.io.IOException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.Test;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
@@ -29,148 +29,148 @@ import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
*
* @author Andy Wilkinson
*/
public class JsonFieldTypesDiscovererTests {
class JsonFieldTypesDiscovererTests {
private final JsonFieldTypesDiscoverer fieldTypeDiscoverer = new JsonFieldTypesDiscoverer();
@Test
public void arrayField() throws IOException {
void arrayField() throws IOException {
assertThat(discoverFieldTypes("[]")).containsExactly(JsonFieldType.ARRAY);
}
@Test
public void topLevelArray() throws IOException {
void topLevelArray() throws IOException {
assertThat(discoverFieldTypes("[]", "[{\"a\":\"alpha\"}]")).containsExactly(JsonFieldType.ARRAY);
}
@Test
public void nestedArray() throws IOException {
void nestedArray() throws IOException {
assertThat(discoverFieldTypes("a[]", "{\"a\": [{\"b\":\"bravo\"}]}")).containsExactly(JsonFieldType.ARRAY);
}
@Test
public void arrayNestedBeneathAnArray() throws IOException {
void arrayNestedBeneathAnArray() throws IOException {
assertThat(discoverFieldTypes("a[].b[]", "{\"a\": [{\"b\": [ 1, 2 ]}]}")).containsExactly(JsonFieldType.ARRAY);
}
@Test
public void specificFieldOfObjectInArrayNestedBeneathAnArray() throws IOException {
void specificFieldOfObjectInArrayNestedBeneathAnArray() throws IOException {
assertThat(discoverFieldTypes("a[].b[].c", "{\"a\": [{\"b\": [ {\"c\": 5}, {\"c\": 5}]}]}"))
.containsExactly(JsonFieldType.NUMBER);
}
@Test
public void booleanField() throws IOException {
void booleanField() throws IOException {
assertThat(discoverFieldTypes("true")).containsExactly(JsonFieldType.BOOLEAN);
}
@Test
public void objectField() throws IOException {
void objectField() throws IOException {
assertThat(discoverFieldTypes("{}")).containsExactly(JsonFieldType.OBJECT);
}
@Test
public void nullField() throws IOException {
void nullField() throws IOException {
assertThat(discoverFieldTypes("null")).containsExactly(JsonFieldType.NULL);
}
@Test
public void numberField() throws IOException {
void numberField() throws IOException {
assertThat(discoverFieldTypes("1.2345")).containsExactly(JsonFieldType.NUMBER);
}
@Test
public void stringField() throws IOException {
void stringField() throws IOException {
assertThat(discoverFieldTypes("\"Foo\"")).containsExactly(JsonFieldType.STRING);
}
@Test
public void nestedField() throws IOException {
void nestedField() throws IOException {
assertThat(discoverFieldTypes("a.b.c", "{\"a\":{\"b\":{\"c\":{}}}}")).containsExactly(JsonFieldType.OBJECT);
}
@Test
public void multipleFieldsWithSameType() throws IOException {
void multipleFieldsWithSameType() throws IOException {
assertThat(discoverFieldTypes("a[].id", "{\"a\":[{\"id\":1},{\"id\":2}]}"))
.containsExactly(JsonFieldType.NUMBER);
}
@Test
public void multipleFieldsWithDifferentTypes() throws IOException {
void multipleFieldsWithDifferentTypes() throws IOException {
assertThat(discoverFieldTypes("a[].id", "{\"a\":[{\"id\":1},{\"id\":true}]}"))
.containsExactlyInAnyOrder(JsonFieldType.NUMBER, JsonFieldType.BOOLEAN);
}
@Test
public void multipleFieldsWithDifferentTypesAndSometimesAbsent() throws IOException {
void multipleFieldsWithDifferentTypesAndSometimesAbsent() throws IOException {
assertThat(discoverFieldTypes("a[].id", "{\"a\":[{\"id\":1},{\"id\":true}, {}]}"))
.containsExactlyInAnyOrder(JsonFieldType.NUMBER, JsonFieldType.BOOLEAN, JsonFieldType.NULL);
}
@Test
public void multipleFieldsWhenSometimesAbsent() throws IOException {
void multipleFieldsWhenSometimesAbsent() throws IOException {
assertThat(discoverFieldTypes("a[].id", "{\"a\":[{\"id\":1},{\"id\":2}, {}]}"))
.containsExactlyInAnyOrder(JsonFieldType.NUMBER, JsonFieldType.NULL);
}
@Test
public void multipleFieldsWhenSometimesNull() throws IOException {
void multipleFieldsWhenSometimesNull() throws IOException {
assertThat(discoverFieldTypes("a[].id", "{\"a\":[{\"id\":1},{\"id\":2}, {\"id\":null}]}"))
.containsExactlyInAnyOrder(JsonFieldType.NUMBER, JsonFieldType.NULL);
}
@Test
public void multipleFieldsWithDifferentTypesAndSometimesNull() throws IOException {
void multipleFieldsWithDifferentTypesAndSometimesNull() throws IOException {
assertThat(discoverFieldTypes("a[].id", "{\"a\":[{\"id\":1},{\"id\":true}, {\"id\":null}]}"))
.containsExactlyInAnyOrder(JsonFieldType.NUMBER, JsonFieldType.BOOLEAN, JsonFieldType.NULL);
}
@Test
public void multipleFieldsWhenEitherNullOrAbsent() throws IOException {
void multipleFieldsWhenEitherNullOrAbsent() throws IOException {
assertThat(discoverFieldTypes("a[].id", "{\"a\":[{},{\"id\":null}]}"))
.containsExactlyInAnyOrder(JsonFieldType.NULL);
}
@Test
public void multipleFieldsThatAreAllNull() throws IOException {
void multipleFieldsThatAreAllNull() throws IOException {
assertThat(discoverFieldTypes("a[].id", "{\"a\":[{\"id\":null},{\"id\":null}]}"))
.containsExactlyInAnyOrder(JsonFieldType.NULL);
}
@Test
public void nonExistentSingleFieldProducesFieldDoesNotExistException() {
void nonExistentSingleFieldProducesFieldDoesNotExistException() {
assertThatExceptionOfType(FieldDoesNotExistException.class)
.isThrownBy(() -> discoverFieldTypes("a.b", "{\"a\":{}}"))
.withMessage("The payload does not contain a field with the path 'a.b'");
}
@Test
public void nonExistentMultipleFieldsProducesFieldDoesNotExistException() {
void nonExistentMultipleFieldsProducesFieldDoesNotExistException() {
assertThatExceptionOfType(FieldDoesNotExistException.class)
.isThrownBy(() -> discoverFieldTypes("a[].b", "{\"a\":[{\"c\":1},{\"c\":2}]}"))
.withMessage("The payload does not contain a field with the path 'a[].b'");
}
@Test
public void leafWildcardWithCommonType() throws IOException {
void leafWildcardWithCommonType() throws IOException {
assertThat(discoverFieldTypes("a.*", "{\"a\": {\"b\": 5, \"c\": 6}}"))
.containsExactlyInAnyOrder(JsonFieldType.NUMBER);
}
@Test
public void leafWildcardWithVaryingType() throws IOException {
void leafWildcardWithVaryingType() throws IOException {
assertThat(discoverFieldTypes("a.*", "{\"a\": {\"b\": 5, \"c\": \"six\"}}"))
.containsExactlyInAnyOrder(JsonFieldType.NUMBER, JsonFieldType.STRING);
}
@Test
public void intermediateWildcardWithCommonType() throws IOException {
void intermediateWildcardWithCommonType() throws IOException {
assertThat(discoverFieldTypes("a.*.d", "{\"a\": {\"b\": {\"d\": 4}, \"c\": {\"d\": 5}}}}"))
.containsExactlyInAnyOrder(JsonFieldType.NUMBER);
}
@Test
public void intermediateWildcardWithVaryingType() throws IOException {
void intermediateWildcardWithVaryingType() throws IOException {
assertThat(discoverFieldTypes("a.*.d", "{\"a\": {\"b\": {\"d\": 4}, \"c\": {\"d\": \"four\"}}}}"))
.containsExactlyInAnyOrder(JsonFieldType.NUMBER, JsonFieldType.STRING);
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2023 the original author or authors.
* Copyright 2014-2025 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,7 +18,7 @@ package org.springframework.restdocs.payload;
import java.util.EnumSet;
import org.junit.Test;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
@@ -27,32 +27,32 @@ import static org.assertj.core.api.Assertions.assertThat;
*
* @author Andy Wilkinson
*/
public class JsonFieldTypesTests {
class JsonFieldTypesTests {
@Test
public void singleTypeCoalescesToThatType() {
void singleTypeCoalescesToThatType() {
assertThat(new JsonFieldTypes(JsonFieldType.NUMBER).coalesce(false)).isEqualTo(JsonFieldType.NUMBER);
}
@Test
public void singleTypeCoalescesToThatTypeWhenOptional() {
void singleTypeCoalescesToThatTypeWhenOptional() {
assertThat(new JsonFieldTypes(JsonFieldType.NUMBER).coalesce(true)).isEqualTo(JsonFieldType.NUMBER);
}
@Test
public void multipleTypesCoalescesToVaries() {
void multipleTypesCoalescesToVaries() {
assertThat(new JsonFieldTypes(EnumSet.of(JsonFieldType.ARRAY, JsonFieldType.NUMBER)).coalesce(false))
.isEqualTo(JsonFieldType.VARIES);
}
@Test
public void nullAndNonNullTypesCoalescesToVaries() {
void nullAndNonNullTypesCoalescesToVaries() {
assertThat(new JsonFieldTypes(EnumSet.of(JsonFieldType.ARRAY, JsonFieldType.NULL)).coalesce(false))
.isEqualTo(JsonFieldType.VARIES);
}
@Test
public void nullAndNonNullTypesCoalescesToNonNullTypeWhenOptional() {
void nullAndNonNullTypesCoalescesToNonNullTypeWhenOptional() {
assertThat(new JsonFieldTypes(EnumSet.of(JsonFieldType.ARRAY, JsonFieldType.NULL)).coalesce(true))
.isEqualTo(JsonFieldType.ARRAY);
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2019 the original author or authors.
* Copyright 2014-2025 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,7 +19,7 @@ package org.springframework.restdocs.payload;
import java.util.Arrays;
import java.util.List;
import org.junit.Test;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.restdocs.payload.PayloadDocumentation.applyPathPrefix;
@@ -31,10 +31,10 @@ import static org.springframework.restdocs.snippet.Attributes.key;
*
* @author Andy Wilkinson
*/
public class PayloadDocumentationTests {
class PayloadDocumentationTests {
@Test
public void applyPathPrefixAppliesPrefixToDescriptorPaths() {
void applyPathPrefixAppliesPrefixToDescriptorPaths() {
List<FieldDescriptor> descriptors = applyPathPrefix("alpha.",
Arrays.asList(fieldWithPath("bravo"), fieldWithPath("charlie")));
assertThat(descriptors.size()).isEqualTo(2);
@@ -42,21 +42,21 @@ public class PayloadDocumentationTests {
}
@Test
public void applyPathPrefixCopiesIgnored() {
void applyPathPrefixCopiesIgnored() {
List<FieldDescriptor> descriptors = applyPathPrefix("alpha.", Arrays.asList(fieldWithPath("bravo").ignored()));
assertThat(descriptors.size()).isEqualTo(1);
assertThat(descriptors.get(0).isIgnored()).isTrue();
}
@Test
public void applyPathPrefixCopiesOptional() {
void applyPathPrefixCopiesOptional() {
List<FieldDescriptor> descriptors = applyPathPrefix("alpha.", Arrays.asList(fieldWithPath("bravo").optional()));
assertThat(descriptors.size()).isEqualTo(1);
assertThat(descriptors.get(0).isOptional()).isTrue();
}
@Test
public void applyPathPrefixCopiesDescription() {
void applyPathPrefixCopiesDescription() {
List<FieldDescriptor> descriptors = applyPathPrefix("alpha.",
Arrays.asList(fieldWithPath("bravo").description("Some field")));
assertThat(descriptors.size()).isEqualTo(1);
@@ -64,7 +64,7 @@ public class PayloadDocumentationTests {
}
@Test
public void applyPathPrefixCopiesType() {
void applyPathPrefixCopiesType() {
List<FieldDescriptor> descriptors = applyPathPrefix("alpha.",
Arrays.asList(fieldWithPath("bravo").type(JsonFieldType.OBJECT)));
assertThat(descriptors.size()).isEqualTo(1);
@@ -72,7 +72,7 @@ public class PayloadDocumentationTests {
}
@Test
public void applyPathPrefixCopiesAttributes() {
void applyPathPrefixCopiesAttributes() {
List<FieldDescriptor> descriptors = applyPathPrefix("alpha.",
Arrays.asList(fieldWithPath("bravo").attributes(key("a").value("alpha"), key("b").value("bravo"))));
assertThat(descriptors.size()).isEqualTo(1);

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2023 the original author or authors.
* Copyright 2014-2025 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,19 +18,14 @@ package org.springframework.restdocs.payload;
import java.io.IOException;
import org.junit.Test;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.restdocs.AbstractSnippetTests;
import org.springframework.restdocs.templates.TemplateEngine;
import org.springframework.restdocs.templates.TemplateFormat;
import org.springframework.restdocs.templates.TemplateResourceResolver;
import org.springframework.restdocs.templates.mustache.MustacheTemplateEngine;
import org.springframework.restdocs.testfixtures.jupiter.AssertableSnippets;
import org.springframework.restdocs.testfixtures.jupiter.OperationBuilder;
import org.springframework.restdocs.testfixtures.jupiter.RenderedSnippetTest;
import org.springframework.restdocs.testfixtures.jupiter.SnippetTemplate;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
import static org.springframework.restdocs.payload.PayloadDocumentation.beneathPath;
import static org.springframework.restdocs.payload.PayloadDocumentation.requestPartBody;
import static org.springframework.restdocs.snippet.Attributes.attributes;
@@ -41,89 +36,84 @@ import static org.springframework.restdocs.snippet.Attributes.key;
*
* @author Andy Wilkinson
*/
public class RequestBodyPartSnippetTests extends AbstractSnippetTests {
class RequestBodyPartSnippetTests {
public RequestBodyPartSnippetTests(String name, TemplateFormat templateFormat) {
super(name, templateFormat);
}
@Test
public void requestPartWithBody() throws IOException {
@RenderedSnippetTest
void requestPartWithBody(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
requestPartBody("one")
.document(this.operationBuilder.request("http://localhost").part("one", "some content".getBytes()).build());
assertThat(this.generatedSnippets.snippet("request-part-one-body"))
.is(codeBlock(null, "nowrap").withContent("some content"));
.document(operationBuilder.request("http://localhost").part("one", "some content".getBytes()).build());
assertThat(snippets.requestPartBody("one"))
.isCodeBlock((codeBlock) -> codeBlock.withOptions("nowrap").content("some content"));
}
@Test
public void requestPartWithNoBody() throws IOException {
requestPartBody("one")
.document(this.operationBuilder.request("http://localhost").part("one", new byte[0]).build());
assertThat(this.generatedSnippets.snippet("request-part-one-body"))
.is(codeBlock(null, "nowrap").withContent(""));
@RenderedSnippetTest
void requestPartWithNoBody(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
requestPartBody("one").document(operationBuilder.request("http://localhost").part("one", new byte[0]).build());
assertThat(snippets.requestPartBody("one"))
.isCodeBlock((codeBlock) -> codeBlock.withOptions("nowrap").content(""));
}
@Test
public void requestPartWithJsonMediaType() throws IOException {
requestPartBody("one").document(this.operationBuilder.request("http://localhost")
@RenderedSnippetTest
void requestPartWithJsonMediaType(OperationBuilder operationBuilder, AssertableSnippets snippets)
throws IOException {
requestPartBody("one").document(operationBuilder.request("http://localhost")
.part("one", "".getBytes())
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
.build());
assertThat(this.generatedSnippets.snippet("request-part-one-body"))
.is(codeBlock("json", "nowrap").withContent(""));
assertThat(snippets.requestPartBody("one"))
.isCodeBlock((codeBlock) -> codeBlock.withLanguageAndOptions("json", "nowrap").content(""));
}
@Test
public void requestPartWithJsonSubtypeMediaType() throws IOException {
requestPartBody("one").document(this.operationBuilder.request("http://localhost")
@RenderedSnippetTest
void requestPartWithJsonSubtypeMediaType(OperationBuilder operationBuilder, AssertableSnippets snippets)
throws IOException {
requestPartBody("one").document(operationBuilder.request("http://localhost")
.part("one", "".getBytes())
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_PROBLEM_JSON_VALUE)
.build());
assertThat(this.generatedSnippets.snippet("request-part-one-body"))
.is(codeBlock("json", "nowrap").withContent(""));
assertThat(snippets.requestPartBody("one"))
.isCodeBlock((codeBlock) -> codeBlock.withLanguageAndOptions("json", "nowrap").content(""));
}
@Test
public void requestPartWithXmlMediaType() throws IOException {
requestPartBody("one").document(this.operationBuilder.request("http://localhost")
@RenderedSnippetTest
void requestPartWithXmlMediaType(OperationBuilder operationBuilder, AssertableSnippets snippets)
throws IOException {
requestPartBody("one").document(operationBuilder.request("http://localhost")
.part("one", "".getBytes())
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_XML_VALUE)
.build());
assertThat(this.generatedSnippets.snippet("request-part-one-body"))
.is(codeBlock("xml", "nowrap").withContent(""));
assertThat(snippets.requestPartBody("one"))
.isCodeBlock((codeBlock) -> codeBlock.withLanguageAndOptions("xml", "nowrap").content(""));
}
@Test
public void requestPartWithXmlSubtypeMediaType() throws IOException {
requestPartBody("one").document(this.operationBuilder.request("http://localhost")
@RenderedSnippetTest
void requestPartWithXmlSubtypeMediaType(OperationBuilder operationBuilder, AssertableSnippets snippets)
throws IOException {
requestPartBody("one").document(operationBuilder.request("http://localhost")
.part("one", "".getBytes())
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_ATOM_XML_VALUE)
.build());
assertThat(this.generatedSnippets.snippet("request-part-one-body"))
.is(codeBlock("xml", "nowrap").withContent(""));
assertThat(snippets.requestPartBody("one"))
.isCodeBlock((codeBlock) -> codeBlock.withLanguageAndOptions("xml", "nowrap").content(""));
}
@Test
public void subsectionOfRequestPartBody() throws IOException {
requestPartBody("one", beneathPath("a.b")).document(this.operationBuilder.request("http://localhost")
@RenderedSnippetTest
void subsectionOfRequestPartBody(OperationBuilder operationBuilder, AssertableSnippets snippets)
throws IOException {
requestPartBody("one", beneathPath("a.b")).document(operationBuilder.request("http://localhost")
.part("one", "{\"a\":{\"b\":{\"c\":5}}}".getBytes())
.build());
assertThat(this.generatedSnippets.snippet("request-part-one-body-beneath-a.b"))
.is(codeBlock(null, "nowrap").withContent("{\"c\":5}"));
assertThat(snippets.requestPartBody("one", "beneath-a.b"))
.isCodeBlock((codeBlock) -> codeBlock.withOptions("nowrap").content("{\"c\":5}"));
}
@Test
public void customSnippetAttributes() throws IOException {
TemplateResourceResolver resolver = mock(TemplateResourceResolver.class);
given(resolver.resolveTemplateResource("request-part-body"))
.willReturn(snippetResource("request-part-body-with-language"));
requestPartBody("one", attributes(key("language").value("json"))).document(
this.operationBuilder.attribute(TemplateEngine.class.getName(), new MustacheTemplateEngine(resolver))
.request("http://localhost")
.part("one", "{\"a\":\"alpha\"}".getBytes())
.build());
assertThat(this.generatedSnippets.snippet("request-part-one-body"))
.is(codeBlock("json", "nowrap").withContent("{\"a\":\"alpha\"}"));
@RenderedSnippetTest
@SnippetTemplate(snippet = "request-part-body", template = "request-part-body-with-language")
void customSnippetAttributes(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
requestPartBody("one", attributes(key("language").value("json")))
.document(operationBuilder.request("http://localhost").part("one", "{\"a\":\"alpha\"}".getBytes()).build());
assertThat(snippets.requestPartBody("one")).isCodeBlock(
(codeBlock) -> codeBlock.withLanguageAndOptions("json", "nowrap").content("{\"a\":\"alpha\"}"));
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2023 the original author or authors.
* Copyright 2014-2025 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,19 +18,14 @@ package org.springframework.restdocs.payload;
import java.io.IOException;
import org.junit.Test;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.restdocs.AbstractSnippetTests;
import org.springframework.restdocs.templates.TemplateEngine;
import org.springframework.restdocs.templates.TemplateFormat;
import org.springframework.restdocs.templates.TemplateResourceResolver;
import org.springframework.restdocs.templates.mustache.MustacheTemplateEngine;
import org.springframework.restdocs.testfixtures.jupiter.AssertableSnippets;
import org.springframework.restdocs.testfixtures.jupiter.OperationBuilder;
import org.springframework.restdocs.testfixtures.jupiter.RenderedSnippetTest;
import org.springframework.restdocs.testfixtures.jupiter.SnippetTemplate;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
import static org.springframework.restdocs.payload.PayloadDocumentation.beneathPath;
import static org.springframework.restdocs.payload.PayloadDocumentation.requestBody;
import static org.springframework.restdocs.snippet.Attributes.attributes;
@@ -41,77 +36,74 @@ import static org.springframework.restdocs.snippet.Attributes.key;
*
* @author Andy Wilkinson
*/
public class RequestBodySnippetTests extends AbstractSnippetTests {
class RequestBodySnippetTests {
public RequestBodySnippetTests(String name, TemplateFormat templateFormat) {
super(name, templateFormat);
@RenderedSnippetTest
void requestWithBody(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
requestBody().document(operationBuilder.request("http://localhost").content("some content").build());
assertThat(snippets.requestBody())
.isCodeBlock((codeBlock) -> codeBlock.withOptions("nowrap").content("some content"));
}
@Test
public void requestWithBody() throws IOException {
requestBody().document(this.operationBuilder.request("http://localhost").content("some content").build());
assertThat(this.generatedSnippets.snippet("request-body"))
.is(codeBlock(null, "nowrap").withContent("some content"));
@RenderedSnippetTest
void requestWithNoBody(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
requestBody().document(operationBuilder.request("http://localhost").build());
assertThat(snippets.requestBody()).isCodeBlock((codeBlock) -> codeBlock.withOptions("nowrap").content(""));
}
@Test
public void requestWithNoBody() throws IOException {
requestBody().document(this.operationBuilder.request("http://localhost").build());
assertThat(this.generatedSnippets.snippet("request-body")).is(codeBlock(null, "nowrap").withContent(""));
}
@Test
public void requestWithJsonMediaType() throws IOException {
requestBody().document(this.operationBuilder.request("http://localhost")
@RenderedSnippetTest
void requestWithJsonMediaType(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
requestBody().document(operationBuilder.request("http://localhost")
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
.build());
assertThat(this.generatedSnippets.snippet("request-body")).is(codeBlock("json", "nowrap").withContent(""));
assertThat(snippets.requestBody())
.isCodeBlock((codeBlock) -> codeBlock.withLanguageAndOptions("json", "nowrap").content(""));
}
@Test
public void requestWithJsonSubtypeMediaType() throws IOException {
requestBody().document(this.operationBuilder.request("http://localhost")
@RenderedSnippetTest
void requestWithJsonSubtypeMediaType(OperationBuilder operationBuilder, AssertableSnippets snippets)
throws IOException {
requestBody().document(operationBuilder.request("http://localhost")
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_PROBLEM_JSON_VALUE)
.build());
assertThat(this.generatedSnippets.snippet("request-body")).is(codeBlock("json", "nowrap").withContent(""));
assertThat(snippets.requestBody())
.isCodeBlock((codeBlock) -> codeBlock.withLanguageAndOptions("json", "nowrap").content(""));
}
@Test
public void requestWithXmlMediaType() throws IOException {
requestBody().document(this.operationBuilder.request("http://localhost")
@RenderedSnippetTest
void requestWithXmlMediaType(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
requestBody().document(operationBuilder.request("http://localhost")
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_XML_VALUE)
.build());
assertThat(this.generatedSnippets.snippet("request-body")).is(codeBlock("xml", "nowrap").withContent(""));
assertThat(snippets.requestBody())
.isCodeBlock((codeBlock) -> codeBlock.withLanguageAndOptions("xml", "nowrap").content(""));
}
@Test
public void requestWithXmlSubtypeMediaType() throws IOException {
requestBody().document(this.operationBuilder.request("http://localhost")
@RenderedSnippetTest
void requestWithXmlSubtypeMediaType(OperationBuilder operationBuilder, AssertableSnippets snippets)
throws IOException {
requestBody().document(operationBuilder.request("http://localhost")
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_ATOM_XML_VALUE)
.build());
assertThat(this.generatedSnippets.snippet("request-body")).is(codeBlock("xml", "nowrap").withContent(""));
assertThat(snippets.requestBody())
.isCodeBlock((codeBlock) -> codeBlock.withLanguageAndOptions("xml", "nowrap").content(""));
}
@Test
public void subsectionOfRequestBody() throws IOException {
@RenderedSnippetTest
void subsectionOfRequestBody(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
requestBody(beneathPath("a.b"))
.document(this.operationBuilder.request("http://localhost").content("{\"a\":{\"b\":{\"c\":5}}}").build());
assertThat(this.generatedSnippets.snippet("request-body-beneath-a.b"))
.is(codeBlock(null, "nowrap").withContent("{\"c\":5}"));
.document(operationBuilder.request("http://localhost").content("{\"a\":{\"b\":{\"c\":5}}}").build());
assertThat(snippets.requestBody("beneath-a.b"))
.isCodeBlock((codeBlock) -> codeBlock.withOptions("nowrap").content("{\"c\":5}"));
}
@Test
public void customSnippetAttributes() throws IOException {
TemplateResourceResolver resolver = mock(TemplateResourceResolver.class);
given(resolver.resolveTemplateResource("request-body"))
.willReturn(snippetResource("request-body-with-language"));
requestBody(attributes(key("language").value("json"))).document(
this.operationBuilder.attribute(TemplateEngine.class.getName(), new MustacheTemplateEngine(resolver))
.request("http://localhost")
.content("{\"a\":\"alpha\"}")
.build());
assertThat(this.generatedSnippets.snippet("request-body"))
.is(codeBlock("json", "nowrap").withContent("{\"a\":\"alpha\"}"));
@RenderedSnippetTest
@SnippetTemplate(snippet = "request-body", template = "request-body-with-language")
void customSnippetAttributes(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
requestBody(attributes(key("language").value("json")))
.document(operationBuilder.request("http://localhost").content("{\"a\":\"alpha\"}").build());
assertThat(snippets.requestBody()).isCodeBlock(
(codeBlock) -> codeBlock.withLanguageAndOptions("json", "nowrap").content("{\"a\":\"alpha\"}"));
}
}

View File

@@ -1,196 +0,0 @@
/*
* Copyright 2014-2023 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.payload;
import java.util.Arrays;
import java.util.Collections;
import org.junit.Rule;
import org.junit.Test;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.restdocs.snippet.SnippetException;
import org.springframework.restdocs.templates.TemplateFormats;
import org.springframework.restdocs.testfixtures.OperationBuilder;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
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 OperationBuilder operationBuilder = new OperationBuilder(TemplateFormats.asciidoctor());
@Test
public void undocumentedRequestField() {
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() {
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() {
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() {
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() {
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() {
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() {
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() {
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() {
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() {
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() {
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() {
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() {
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() {
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() {
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-2024 the original author or authors.
* Copyright 2014-2025 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,21 +18,19 @@ package org.springframework.restdocs.payload;
import java.io.IOException;
import java.util.Arrays;
import org.junit.Test;
import java.util.Collections;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.restdocs.AbstractSnippetTests;
import org.springframework.restdocs.templates.TemplateEngine;
import org.springframework.restdocs.templates.TemplateFormat;
import org.springframework.restdocs.templates.TemplateFormats;
import org.springframework.restdocs.templates.TemplateResourceResolver;
import org.springframework.restdocs.templates.mustache.MustacheTemplateEngine;
import org.springframework.restdocs.snippet.SnippetException;
import org.springframework.restdocs.testfixtures.jupiter.AssertableSnippets;
import org.springframework.restdocs.testfixtures.jupiter.OperationBuilder;
import org.springframework.restdocs.testfixtures.jupiter.RenderedSnippetTest;
import org.springframework.restdocs.testfixtures.jupiter.SnippetTemplate;
import org.springframework.restdocs.testfixtures.jupiter.SnippetTest;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.springframework.restdocs.payload.PayloadDocumentation.beneathPath;
import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath;
import static org.springframework.restdocs.payload.PayloadDocumentation.requestFields;
@@ -46,342 +44,492 @@ import static org.springframework.restdocs.snippet.Attributes.key;
* @author Andy Wilkinson
* @author Sungjun Lee
*/
public class RequestFieldsSnippetTests extends AbstractSnippetTests {
class RequestFieldsSnippetTests {
public RequestFieldsSnippetTests(String name, TemplateFormat templateFormat) {
super(name, templateFormat);
}
@Test
public void mapRequestWithFields() throws IOException {
@RenderedSnippetTest
void mapRequestWithFields(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
new RequestFieldsSnippet(Arrays.asList(fieldWithPath("a.b").description("one"),
fieldWithPath("a.c").description("two"), fieldWithPath("a").description("three")))
.document(this.operationBuilder.request("http://localhost")
.document(operationBuilder.request("http://localhost")
.content("{\"a\": {\"b\": 5, \"c\": \"charlie\"}}")
.build());
assertThat(this.generatedSnippets.requestFields())
.is(tableWithHeader("Path", "Type", "Description").row("`a.b`", "`Number`", "one")
.row("`a.c`", "`String`", "two")
.row("`a`", "`Object`", "three"));
assertThat(snippets.requestFields()).isTable((table) -> table.withHeader("Path", "Type", "Description")
.row("`a.b`", "`Number`", "one")
.row("`a.c`", "`String`", "two")
.row("`a`", "`Object`", "three"));
}
@Test
public void mapRequestWithNullField() throws IOException {
@RenderedSnippetTest
void mapRequestWithNullField(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
new RequestFieldsSnippet(Arrays.asList(fieldWithPath("a.b").description("one")))
.document(this.operationBuilder.request("http://localhost").content("{\"a\": {\"b\": null}}").build());
assertThat(this.generatedSnippets.requestFields())
.is(tableWithHeader("Path", "Type", "Description").row("`a.b`", "`Null`", "one"));
.document(operationBuilder.request("http://localhost").content("{\"a\": {\"b\": null}}").build());
assertThat(snippets.requestFields())
.isTable((table) -> table.withHeader("Path", "Type", "Description").row("`a.b`", "`Null`", "one"));
}
@Test
public void entireSubsectionsCanBeDocumented() throws IOException {
@RenderedSnippetTest
void entireSubsectionsCanBeDocumented(OperationBuilder operationBuilder, AssertableSnippets snippets)
throws IOException {
new RequestFieldsSnippet(Arrays.asList(subsectionWithPath("a").description("one")))
.document(this.operationBuilder.request("http://localhost")
.document(operationBuilder.request("http://localhost")
.content("{\"a\": {\"b\": 5, \"c\": \"charlie\"}}")
.build());
assertThat(this.generatedSnippets.requestFields())
.is(tableWithHeader("Path", "Type", "Description").row("`a`", "`Object`", "one"));
assertThat(snippets.requestFields())
.isTable((table) -> table.withHeader("Path", "Type", "Description").row("`a`", "`Object`", "one"));
}
@Test
public void subsectionOfMapRequest() throws IOException {
@RenderedSnippetTest
void subsectionOfMapRequest(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
requestFields(beneathPath("a"), fieldWithPath("b").description("one"), fieldWithPath("c").description("two"))
.document(this.operationBuilder.request("http://localhost")
.document(operationBuilder.request("http://localhost")
.content("{\"a\": {\"b\": 5, \"c\": \"charlie\"}}")
.build());
assertThat(this.generatedSnippets.snippet("request-fields-beneath-a"))
.is(tableWithHeader("Path", "Type", "Description").row("`b`", "`Number`", "one")
assertThat(snippets.requestFields("beneath-a"))
.isTable((table) -> table.withHeader("Path", "Type", "Description")
.row("`b`", "`Number`", "one")
.row("`c`", "`String`", "two"));
}
@Test
public void subsectionOfMapRequestWithCommonPrefix() throws IOException {
@RenderedSnippetTest
void subsectionOfMapRequestWithCommonPrefix(OperationBuilder operationBuilder, AssertableSnippets snippets)
throws IOException {
requestFields(beneathPath("a")).andWithPrefix("b.", fieldWithPath("c").description("two"))
.document(this.operationBuilder.request("http://localhost")
.document(operationBuilder.request("http://localhost")
.content("{\"a\": {\"b\": {\"c\": \"charlie\"}}}")
.build());
assertThat(this.generatedSnippets.snippet("request-fields-beneath-a"))
.is(tableWithHeader("Path", "Type", "Description").row("`b.c`", "`String`", "two"));
assertThat(snippets.requestFields("beneath-a"))
.isTable((table) -> table.withHeader("Path", "Type", "Description").row("`b.c`", "`String`", "two"));
}
@Test
public void arrayRequestWithFields() throws IOException {
@RenderedSnippetTest
void arrayRequestWithFields(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
new RequestFieldsSnippet(
Arrays.asList(fieldWithPath("[]").description("one"), fieldWithPath("[]a.b").description("two"),
fieldWithPath("[]a.c").description("three"), fieldWithPath("[]a").description("four")))
.document(this.operationBuilder.request("http://localhost")
.document(operationBuilder.request("http://localhost")
.content("[{\"a\": {\"b\": 5, \"c\":\"charlie\"}}," + "{\"a\": {\"b\": 4, \"c\":\"chalk\"}}]")
.build());
assertThat(this.generatedSnippets.requestFields())
.is(tableWithHeader("Path", "Type", "Description").row("`[]`", "`Array`", "one")
.row("`[]a.b`", "`Number`", "two")
.row("`[]a.c`", "`String`", "three")
.row("`[]a`", "`Object`", "four"));
assertThat(snippets.requestFields()).isTable((table) -> table.withHeader("Path", "Type", "Description")
.row("`[]`", "`Array`", "one")
.row("`[]a.b`", "`Number`", "two")
.row("`[]a.c`", "`String`", "three")
.row("`[]a`", "`Object`", "four"));
}
@Test
public void arrayRequestWithAlwaysNullField() throws IOException {
@RenderedSnippetTest
void arrayRequestWithAlwaysNullField(OperationBuilder operationBuilder, AssertableSnippets snippets)
throws IOException {
new RequestFieldsSnippet(Arrays.asList(fieldWithPath("[]a.b").description("one")))
.document(this.operationBuilder.request("http://localhost")
.document(operationBuilder.request("http://localhost")
.content("[{\"a\": {\"b\": null}}," + "{\"a\": {\"b\": null}}]")
.build());
assertThat(this.generatedSnippets.requestFields())
.is(tableWithHeader("Path", "Type", "Description").row("`[]a.b`", "`Null`", "one"));
assertThat(snippets.requestFields())
.isTable((table) -> table.withHeader("Path", "Type", "Description").row("`[]a.b`", "`Null`", "one"));
}
@Test
public void subsectionOfArrayRequest() throws IOException {
@RenderedSnippetTest
void subsectionOfArrayRequest(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
requestFields(beneathPath("[].a"), fieldWithPath("b").description("one"), fieldWithPath("c").description("two"))
.document(this.operationBuilder.request("http://localhost")
.document(operationBuilder.request("http://localhost")
.content("[{\"a\": {\"b\": 5, \"c\": \"charlie\"}}]")
.build());
assertThat(this.generatedSnippets.snippet("request-fields-beneath-[].a"))
.is(tableWithHeader("Path", "Type", "Description").row("`b`", "`Number`", "one")
assertThat(snippets.requestFields("beneath-[].a"))
.isTable((table) -> table.withHeader("Path", "Type", "Description")
.row("`b`", "`Number`", "one")
.row("`c`", "`String`", "two"));
}
@Test
public void ignoredRequestField() throws IOException {
@RenderedSnippetTest
void ignoredRequestField(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
new RequestFieldsSnippet(Arrays.asList(fieldWithPath("a").ignored(), fieldWithPath("b").description("Field b")))
.document(this.operationBuilder.request("http://localhost").content("{\"a\": 5, \"b\": 4}").build());
assertThat(this.generatedSnippets.requestFields())
.is(tableWithHeader("Path", "Type", "Description").row("`b`", "`Number`", "Field b"));
.document(operationBuilder.request("http://localhost").content("{\"a\": 5, \"b\": 4}").build());
assertThat(snippets.requestFields())
.isTable((table) -> table.withHeader("Path", "Type", "Description").row("`b`", "`Number`", "Field b"));
}
@Test
public void entireSubsectionCanBeIgnored() throws IOException {
@RenderedSnippetTest
void entireSubsectionCanBeIgnored(OperationBuilder operationBuilder, AssertableSnippets snippets)
throws IOException {
new RequestFieldsSnippet(
Arrays.asList(subsectionWithPath("a").ignored(), fieldWithPath("c").description("Field c")))
.document(
this.operationBuilder.request("http://localhost").content("{\"a\": {\"b\": 5}, \"c\": 4}").build());
assertThat(this.generatedSnippets.requestFields())
.is(tableWithHeader("Path", "Type", "Description").row("`c`", "`Number`", "Field c"));
.document(operationBuilder.request("http://localhost").content("{\"a\": {\"b\": 5}, \"c\": 4}").build());
assertThat(snippets.requestFields())
.isTable((table) -> table.withHeader("Path", "Type", "Description").row("`c`", "`Number`", "Field c"));
}
@Test
public void allUndocumentedRequestFieldsCanBeIgnored() throws IOException {
@RenderedSnippetTest
void allUndocumentedRequestFieldsCanBeIgnored(OperationBuilder operationBuilder, AssertableSnippets snippets)
throws IOException {
new RequestFieldsSnippet(Arrays.asList(fieldWithPath("b").description("Field b")), true)
.document(this.operationBuilder.request("http://localhost").content("{\"a\": 5, \"b\": 4}").build());
assertThat(this.generatedSnippets.requestFields())
.is(tableWithHeader("Path", "Type", "Description").row("`b`", "`Number`", "Field b"));
.document(operationBuilder.request("http://localhost").content("{\"a\": 5, \"b\": 4}").build());
assertThat(snippets.requestFields())
.isTable((table) -> table.withHeader("Path", "Type", "Description").row("`b`", "`Number`", "Field b"));
}
@Test
public void allUndocumentedFieldsContinueToBeIgnoredAfterAddingDescriptors() throws IOException {
@RenderedSnippetTest
void allUndocumentedFieldsContinueToBeIgnoredAfterAddingDescriptors(OperationBuilder operationBuilder,
AssertableSnippets snippets) throws IOException {
new RequestFieldsSnippet(Arrays.asList(fieldWithPath("b").description("Field b")), true)
.andWithPrefix("c.", fieldWithPath("d").description("Field d"))
.document(this.operationBuilder.request("http://localhost")
.content("{\"a\":5,\"b\":4,\"c\":{\"d\": 3}}")
.build());
assertThat(this.generatedSnippets.requestFields())
.is(tableWithHeader("Path", "Type", "Description").row("`b`", "`Number`", "Field b")
.row("`c.d`", "`Number`", "Field d"));
}
@Test
public void missingOptionalRequestField() throws IOException {
new RequestFieldsSnippet(
Arrays.asList(fieldWithPath("a.b").description("one").type(JsonFieldType.STRING).optional()))
.document(this.operationBuilder.request("http://localhost").content("{}").build());
assertThat(this.generatedSnippets.requestFields())
.is(tableWithHeader("Path", "Type", "Description").row("`a.b`", "`String`", "one"));
}
@Test
public void missingIgnoredOptionalRequestFieldDoesNotRequireAType() throws IOException {
new RequestFieldsSnippet(Arrays.asList(fieldWithPath("a.b").description("one").ignored().optional()))
.document(this.operationBuilder.request("http://localhost").content("{}").build());
assertThat(this.generatedSnippets.requestFields()).is(tableWithHeader("Path", "Type", "Description"));
}
@Test
public void presentOptionalRequestField() throws IOException {
new RequestFieldsSnippet(
Arrays.asList(fieldWithPath("a.b").description("one").type(JsonFieldType.STRING).optional()))
.document(
this.operationBuilder.request("http://localhost").content("{\"a\": { \"b\": \"bravo\"}}").build());
assertThat(this.generatedSnippets.requestFields())
.is(tableWithHeader("Path", "Type", "Description").row("`a.b`", "`String`", "one"));
operationBuilder.request("http://localhost").content("{\"a\":5,\"b\":4,\"c\":{\"d\": 3}}").build());
assertThat(snippets.requestFields()).isTable((table) -> table.withHeader("Path", "Type", "Description")
.row("`b`", "`Number`", "Field b")
.row("`c.d`", "`Number`", "Field d"));
}
@Test
public void requestFieldsWithCustomAttributes() throws IOException {
TemplateResourceResolver resolver = mock(TemplateResourceResolver.class);
given(resolver.resolveTemplateResource("request-fields"))
.willReturn(snippetResource("request-fields-with-title"));
@RenderedSnippetTest
void missingOptionalRequestField(OperationBuilder operationBuilder, AssertableSnippets snippets)
throws IOException {
new RequestFieldsSnippet(
Arrays.asList(fieldWithPath("a.b").description("one").type(JsonFieldType.STRING).optional()))
.document(operationBuilder.request("http://localhost").content("{}").build());
assertThat(snippets.requestFields())
.isTable((table) -> table.withHeader("Path", "Type", "Description").row("`a.b`", "`String`", "one"));
}
@RenderedSnippetTest
void missingIgnoredOptionalRequestFieldDoesNotRequireAType(OperationBuilder operationBuilder,
AssertableSnippets snippets) throws IOException {
new RequestFieldsSnippet(Arrays.asList(fieldWithPath("a.b").description("one").ignored().optional()))
.document(operationBuilder.request("http://localhost").content("{}").build());
assertThat(snippets.requestFields()).isTable((table) -> table.withHeader("Path", "Type", "Description"));
}
@RenderedSnippetTest
void presentOptionalRequestField(OperationBuilder operationBuilder, AssertableSnippets snippets)
throws IOException {
new RequestFieldsSnippet(
Arrays.asList(fieldWithPath("a.b").description("one").type(JsonFieldType.STRING).optional()))
.document(operationBuilder.request("http://localhost").content("{\"a\": { \"b\": \"bravo\"}}").build());
assertThat(snippets.requestFields())
.isTable((table) -> table.withHeader("Path", "Type", "Description").row("`a.b`", "`String`", "one"));
}
@RenderedSnippetTest
@SnippetTemplate(snippet = "request-fields", template = "request-fields-with-title")
void requestFieldsWithCustomAttributes(OperationBuilder operationBuilder, AssertableSnippets snippets)
throws IOException {
new RequestFieldsSnippet(Arrays.asList(fieldWithPath("a").description("one")),
attributes(key("title").value("Custom title")))
.document(this.operationBuilder
.attribute(TemplateEngine.class.getName(), new MustacheTemplateEngine(resolver))
.request("http://localhost")
.content("{\"a\": \"foo\"}")
.build());
assertThat(this.generatedSnippets.requestFields()).contains("Custom title");
.document(operationBuilder.request("http://localhost").content("{\"a\": \"foo\"}").build());
assertThat(snippets.requestFields())
.isTable((table) -> table.withTitleAndHeader("Custom title", "Path", "Type", "Description")
.row("a", "String", "one"));
}
@Test
public void requestFieldsWithCustomDescriptorAttributes() throws IOException {
TemplateResourceResolver resolver = mock(TemplateResourceResolver.class);
given(resolver.resolveTemplateResource("request-fields"))
.willReturn(snippetResource("request-fields-with-extra-column"));
@RenderedSnippetTest
@SnippetTemplate(snippet = "request-fields", template = "request-fields-with-extra-column")
void requestFieldsWithCustomDescriptorAttributes(OperationBuilder operationBuilder, AssertableSnippets snippets)
throws IOException {
new RequestFieldsSnippet(
Arrays.asList(fieldWithPath("a.b").description("one").attributes(key("foo").value("alpha")),
fieldWithPath("a.c").description("two").attributes(key("foo").value("bravo")),
fieldWithPath("a").description("three").attributes(key("foo").value("charlie"))))
.document(this.operationBuilder
.attribute(TemplateEngine.class.getName(), new MustacheTemplateEngine(resolver))
.request("http://localhost")
.document(operationBuilder.request("http://localhost")
.content("{\"a\": {\"b\": 5, \"c\": \"charlie\"}}")
.build());
assertThat(this.generatedSnippets.requestFields())
.is(tableWithHeader("Path", "Type", "Description", "Foo").row("a.b", "Number", "one", "alpha")
.row("a.c", "String", "two", "bravo")
.row("a", "Object", "three", "charlie"));
assertThat(snippets.requestFields()).isTable((table) -> table.withHeader("Path", "Type", "Description", "Foo")
.row("a.b", "Number", "one", "alpha")
.row("a.c", "String", "two", "bravo")
.row("a", "Object", "three", "charlie"));
}
@Test
public void fieldWithExplicitExactlyMatchingType() throws IOException {
@RenderedSnippetTest
void fieldWithExplicitExactlyMatchingType(OperationBuilder operationBuilder, AssertableSnippets snippets)
throws IOException {
new RequestFieldsSnippet(Arrays.asList(fieldWithPath("a").description("one").type(JsonFieldType.NUMBER)))
.document(this.operationBuilder.request("http://localhost").content("{\"a\": 5 }").build());
assertThat(this.generatedSnippets.requestFields())
.is(tableWithHeader("Path", "Type", "Description").row("`a`", "`Number`", "one"));
.document(operationBuilder.request("http://localhost").content("{\"a\": 5 }").build());
assertThat(snippets.requestFields())
.isTable((table) -> table.withHeader("Path", "Type", "Description").row("`a`", "`Number`", "one"));
}
@Test
public void fieldWithExplicitVariesType() throws IOException {
@RenderedSnippetTest
void fieldWithExplicitVariesType(OperationBuilder operationBuilder, AssertableSnippets snippets)
throws IOException {
new RequestFieldsSnippet(Arrays.asList(fieldWithPath("a").description("one").type(JsonFieldType.VARIES)))
.document(this.operationBuilder.request("http://localhost").content("{\"a\": 5 }").build());
assertThat(this.generatedSnippets.requestFields())
.is(tableWithHeader("Path", "Type", "Description").row("`a`", "`Varies`", "one"));
.document(operationBuilder.request("http://localhost").content("{\"a\": 5 }").build());
assertThat(snippets.requestFields())
.isTable((table) -> table.withHeader("Path", "Type", "Description").row("`a`", "`Varies`", "one"));
}
@Test
public void applicationXmlRequestFields() throws IOException {
xmlRequestFields(MediaType.APPLICATION_XML);
@RenderedSnippetTest
void applicationXmlRequestFields(OperationBuilder operationBuilder, AssertableSnippets snippets)
throws IOException {
xmlRequestFields(MediaType.APPLICATION_XML, operationBuilder, snippets);
}
@Test
public void textXmlRequestFields() throws IOException {
xmlRequestFields(MediaType.TEXT_XML);
@RenderedSnippetTest
void textXmlRequestFields(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
xmlRequestFields(MediaType.TEXT_XML, operationBuilder, snippets);
}
@Test
public void customXmlRequestFields() throws IOException {
xmlRequestFields(MediaType.parseMediaType("application/vnd.com.example+xml"));
@RenderedSnippetTest
void customXmlRequestFields(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
xmlRequestFields(MediaType.parseMediaType("application/vnd.com.example+xml"), operationBuilder, snippets);
}
private void xmlRequestFields(MediaType contentType) throws IOException {
private void xmlRequestFields(MediaType contentType, OperationBuilder operationBuilder, AssertableSnippets snippets)
throws IOException {
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(this.operationBuilder.request("http://localhost")
.document(operationBuilder.request("http://localhost")
.content("<a><b>5</b><c>charlie</c></a>")
.header(HttpHeaders.CONTENT_TYPE, contentType.toString())
.build());
assertThat(this.generatedSnippets.requestFields())
.is(tableWithHeader("Path", "Type", "Description").row("`a/b`", "`b`", "one")
.row("`a/c`", "`c`", "two")
.row("`a`", "`a`", "three"));
assertThat(snippets.requestFields()).isTable((table) -> table.withHeader("Path", "Type", "Description")
.row("`a/b`", "`b`", "one")
.row("`a/c`", "`c`", "two")
.row("`a`", "`a`", "three"));
}
@Test
public void entireSubsectionOfXmlPayloadCanBeDocumented() throws IOException {
@RenderedSnippetTest
void entireSubsectionOfXmlPayloadCanBeDocumented(OperationBuilder operationBuilder, AssertableSnippets snippets)
throws IOException {
new RequestFieldsSnippet(Arrays.asList(subsectionWithPath("a").description("one").type("a")))
.document(this.operationBuilder.request("http://localhost")
.document(operationBuilder.request("http://localhost")
.content("<a><b>5</b><c>charlie</c></a>")
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_XML_VALUE)
.build());
assertThat(this.generatedSnippets.requestFields())
.is(tableWithHeader("Path", "Type", "Description").row("`a`", "`a`", "one"));
assertThat(snippets.requestFields())
.isTable((table) -> table.withHeader("Path", "Type", "Description").row("`a`", "`a`", "one"));
}
@Test
public void additionalDescriptors() throws IOException {
@RenderedSnippetTest
void additionalDescriptors(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
PayloadDocumentation
.requestFields(fieldWithPath("a.b").description("one"), fieldWithPath("a.c").description("two"))
.and(fieldWithPath("a").description("three"))
.document(this.operationBuilder.request("http://localhost")
.document(operationBuilder.request("http://localhost")
.content("{\"a\": {\"b\": 5, \"c\": \"charlie\"}}")
.build());
assertThat(this.generatedSnippets.requestFields())
.is(tableWithHeader("Path", "Type", "Description").row("`a.b`", "`Number`", "one")
.row("`a.c`", "`String`", "two")
.row("`a`", "`Object`", "three"));
assertThat(snippets.requestFields()).isTable((table) -> table.withHeader("Path", "Type", "Description")
.row("`a.b`", "`Number`", "one")
.row("`a.c`", "`String`", "two")
.row("`a`", "`Object`", "three"));
}
@Test
public void prefixedAdditionalDescriptors() throws IOException {
@RenderedSnippetTest
void prefixedAdditionalDescriptors(OperationBuilder operationBuilder, AssertableSnippets snippets)
throws IOException {
PayloadDocumentation.requestFields(fieldWithPath("a").description("one"))
.andWithPrefix("a.", fieldWithPath("b").description("two"), fieldWithPath("c").description("three"))
.document(this.operationBuilder.request("http://localhost")
.document(operationBuilder.request("http://localhost")
.content("{\"a\": {\"b\": 5, \"c\": \"charlie\"}}")
.build());
assertThat(this.generatedSnippets.requestFields())
.is(tableWithHeader("Path", "Type", "Description").row("`a`", "`Object`", "one")
.row("`a.b`", "`Number`", "two")
.row("`a.c`", "`String`", "three"));
assertThat(snippets.requestFields()).isTable((table) -> table.withHeader("Path", "Type", "Description")
.row("`a`", "`Object`", "one")
.row("`a.b`", "`Number`", "two")
.row("`a.c`", "`String`", "three"));
}
@Test
public void requestWithFieldsWithEscapedContent() throws IOException {
@RenderedSnippetTest
void requestWithFieldsWithEscapedContent(OperationBuilder operationBuilder, AssertableSnippets snippets)
throws IOException {
new RequestFieldsSnippet(Arrays.asList(fieldWithPath("Foo|Bar").type("one|two").description("three|four")))
.document(this.operationBuilder.request("http://localhost").content("{\"Foo|Bar\": 5}").build());
assertThat(this.generatedSnippets.requestFields()).is(tableWithHeader("Path", "Type", "Description")
.row(escapeIfNecessary("`Foo|Bar`"), escapeIfNecessary("`one|two`"), escapeIfNecessary("three|four")));
.document(operationBuilder.request("http://localhost").content("{\"Foo|Bar\": 5}").build());
assertThat(snippets.requestFields()).isTable(
(table) -> table.withHeader("Path", "Type", "Description").row("`Foo|Bar`", "`one|two`", "three|four"));
}
@Test
public void mapRequestWithVaryingKeysMatchedUsingWildcard() throws IOException {
@RenderedSnippetTest
void mapRequestWithVaryingKeysMatchedUsingWildcard(OperationBuilder operationBuilder, AssertableSnippets snippets)
throws IOException {
new RequestFieldsSnippet(Arrays.asList(fieldWithPath("things.*.size").description("one"),
fieldWithPath("things.*.type").description("two")))
.document(this.operationBuilder.request("http://localhost")
.document(operationBuilder.request("http://localhost")
.content("{\"things\": {\"12abf\": {\"type\":" + "\"Whale\", \"size\": \"HUGE\"},"
+ "\"gzM33\" : {\"type\": \"Screw\"," + "\"size\": \"SMALL\"}}}")
.build());
assertThat(this.generatedSnippets.requestFields())
.is(tableWithHeader("Path", "Type", "Description").row("`things.*.size`", "`String`", "one")
.row("`things.*.type`", "`String`", "two"));
assertThat(snippets.requestFields()).isTable((table) -> table.withHeader("Path", "Type", "Description")
.row("`things.*.size`", "`String`", "one")
.row("`things.*.type`", "`String`", "two"));
}
@Test
public void requestWithArrayContainingFieldThatIsSometimesNull() throws IOException {
@RenderedSnippetTest
void requestWithArrayContainingFieldThatIsSometimesNull(OperationBuilder operationBuilder,
AssertableSnippets snippets) throws IOException {
new RequestFieldsSnippet(
Arrays.asList(fieldWithPath("assets[].name").description("one").type(JsonFieldType.STRING).optional()))
.document(this.operationBuilder.request("http://localhost")
.document(operationBuilder.request("http://localhost")
.content("{\"assets\": [" + "{\"name\": \"sample1\"}, " + "{\"name\": null}, "
+ "{\"name\": \"sample2\"}]}")
.build());
assertThat(this.generatedSnippets.requestFields())
.is(tableWithHeader("Path", "Type", "Description").row("`assets[].name`", "`String`", "one"));
assertThat(snippets.requestFields()).isTable(
(table) -> table.withHeader("Path", "Type", "Description").row("`assets[].name`", "`String`", "one"));
}
@Test
public void optionalFieldBeneathArrayThatIsSometimesAbsent() throws IOException {
@RenderedSnippetTest
void optionalFieldBeneathArrayThatIsSometimesAbsent(OperationBuilder operationBuilder, AssertableSnippets snippets)
throws IOException {
new RequestFieldsSnippet(
Arrays.asList(fieldWithPath("a[].b").description("one").type(JsonFieldType.NUMBER).optional(),
fieldWithPath("a[].c").description("two").type(JsonFieldType.NUMBER)))
.document(this.operationBuilder.request("http://localhost")
.document(operationBuilder.request("http://localhost")
.content("{\"a\":[{\"b\": 1,\"c\": 2}, " + "{\"c\": 2}, {\"b\": 1,\"c\": 2}]}")
.build());
assertThat(this.generatedSnippets.requestFields())
.is(tableWithHeader("Path", "Type", "Description").row("`a[].b`", "`Number`", "one")
.row("`a[].c`", "`Number`", "two"));
assertThat(snippets.requestFields()).isTable((table) -> table.withHeader("Path", "Type", "Description")
.row("`a[].b`", "`Number`", "one")
.row("`a[].c`", "`Number`", "two"));
}
@Test
public void typeDeterminationDoesNotSetTypeOnDescriptor() throws IOException {
@RenderedSnippetTest
void typeDeterminationDoesNotSetTypeOnDescriptor(OperationBuilder operationBuilder, AssertableSnippets snippets)
throws IOException {
FieldDescriptor descriptor = fieldWithPath("a.b").description("one");
new RequestFieldsSnippet(Arrays.asList(descriptor))
.document(this.operationBuilder.request("http://localhost").content("{\"a\": {\"b\": 5}}").build());
.document(operationBuilder.request("http://localhost").content("{\"a\": {\"b\": 5}}").build());
assertThat(descriptor.getType()).isNull();
assertThat(this.generatedSnippets.requestFields())
.is(tableWithHeader("Path", "Type", "Description").row("`a.b`", "`Number`", "one"));
assertThat(snippets.requestFields())
.isTable((table) -> table.withHeader("Path", "Type", "Description").row("`a.b`", "`Number`", "one"));
}
private String escapeIfNecessary(String input) {
if (this.templateFormat.getId().equals(TemplateFormats.markdown().getId())) {
return input;
}
return input.replace("|", "\\|");
@SnippetTest
void undocumentedRequestField(OperationBuilder operationBuilder) {
assertThatExceptionOfType(SnippetException.class)
.isThrownBy(() -> new RequestFieldsSnippet(Collections.<FieldDescriptor>emptyList())
.document(operationBuilder.request("http://localhost").content("{\"a\": 5}").build()))
.withMessageStartingWith("The following parts of the payload were not documented:");
}
@SnippetTest
void missingRequestField(OperationBuilder operationBuilder) {
assertThatExceptionOfType(SnippetException.class)
.isThrownBy(() -> new RequestFieldsSnippet(Arrays.asList(fieldWithPath("a.b").description("one")))
.document(operationBuilder.request("http://localhost").content("{}").build()))
.withMessage("Fields with the following paths were not found in the payload: [a.b]");
}
@SnippetTest
void missingOptionalRequestFieldWithNoTypeProvided(OperationBuilder operationBuilder) {
assertThatExceptionOfType(FieldTypeRequiredException.class).isThrownBy(
() -> new RequestFieldsSnippet(Arrays.asList(fieldWithPath("a.b").description("one").optional()))
.document(operationBuilder.request("http://localhost").content("{ }").build()));
}
@SnippetTest
void undocumentedRequestFieldAndMissingRequestField(OperationBuilder operationBuilder) {
assertThatExceptionOfType(SnippetException.class)
.isThrownBy(() -> new RequestFieldsSnippet(Arrays.asList(fieldWithPath("a.b").description("one")))
.document(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]");
}
@SnippetTest
void attemptToDocumentFieldsWithNoRequestBody(OperationBuilder operationBuilder) {
assertThatExceptionOfType(SnippetException.class)
.isThrownBy(() -> new RequestFieldsSnippet(Arrays.asList(fieldWithPath("a").description("one")))
.document(operationBuilder.request("http://localhost").build()))
.withMessage("Cannot document request fields as the request body is empty");
}
@SnippetTest
void fieldWithExplicitTypeThatDoesNotMatchThePayload(OperationBuilder operationBuilder) {
assertThatExceptionOfType(FieldTypesDoNotMatchException.class)
.isThrownBy(() -> new RequestFieldsSnippet(
Arrays.asList(fieldWithPath("a").description("one").type(JsonFieldType.OBJECT)))
.document(operationBuilder.request("http://localhost").content("{ \"a\": 5 }").build()))
.withMessage("The documented type of the field 'a' is Object but the actual type is Number");
}
@SnippetTest
void fieldWithExplicitSpecificTypeThatActuallyVaries(OperationBuilder operationBuilder) {
assertThatExceptionOfType(FieldTypesDoNotMatchException.class).isThrownBy(() -> new RequestFieldsSnippet(
Arrays.asList(fieldWithPath("[].a").description("one").type(JsonFieldType.OBJECT)))
.document(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");
}
@SnippetTest
void undocumentedXmlRequestField(OperationBuilder operationBuilder) {
assertThatExceptionOfType(SnippetException.class)
.isThrownBy(() -> new RequestFieldsSnippet(Collections.<FieldDescriptor>emptyList())
.document(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:");
}
@SnippetTest
void xmlDescendentsAreNotDocumentedByFieldDescriptor(OperationBuilder operationBuilder) {
assertThatExceptionOfType(SnippetException.class)
.isThrownBy(() -> new RequestFieldsSnippet(Arrays.asList(fieldWithPath("a").type("a").description("one")))
.document(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:");
}
@SnippetTest
void xmlRequestFieldWithNoType(OperationBuilder operationBuilder) {
assertThatExceptionOfType(FieldTypeRequiredException.class)
.isThrownBy(() -> new RequestFieldsSnippet(Arrays.asList(fieldWithPath("a").description("one")))
.document(operationBuilder.request("http://localhost")
.content("<a>5</a>")
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_XML_VALUE)
.build()));
}
@SnippetTest
void missingXmlRequestField(OperationBuilder operationBuilder) {
assertThatExceptionOfType(SnippetException.class)
.isThrownBy(() -> new RequestFieldsSnippet(
Arrays.asList(fieldWithPath("a/b").description("one"), fieldWithPath("a").description("one")))
.document(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]");
}
@SnippetTest
void undocumentedXmlRequestFieldAndMissingXmlRequestField(OperationBuilder operationBuilder) {
assertThatExceptionOfType(SnippetException.class)
.isThrownBy(() -> new RequestFieldsSnippet(Arrays.asList(fieldWithPath("a/b").description("one")))
.document(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]");
}
@SnippetTest
void unsupportedContent(OperationBuilder operationBuilder) {
assertThatExceptionOfType(PayloadHandlingException.class)
.isThrownBy(() -> new RequestFieldsSnippet(Collections.<FieldDescriptor>emptyList())
.document(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");
}
@SnippetTest
void nonOptionalFieldBeneathArrayThatIsSometimesNull(OperationBuilder operationBuilder) {
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(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]");
}
@SnippetTest
void nonOptionalFieldBeneathArrayThatIsSometimesAbsent(OperationBuilder operationBuilder) {
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(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,70 +0,0 @@
/*
* Copyright 2014-2023 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.payload;
import java.util.Arrays;
import java.util.Collections;
import org.junit.Rule;
import org.junit.Test;
import org.springframework.restdocs.snippet.SnippetException;
import org.springframework.restdocs.templates.TemplateFormats;
import org.springframework.restdocs.testfixtures.OperationBuilder;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath;
/**
* Tests for failures when rendering {@link RequestPartFieldsSnippet} due to missing or
* undocumented fields.
*
* @author Mathieu Pousse
* @author Andy Wilkinson
*/
public class RequestPartFieldsSnippetFailureTests {
@Rule
public OperationBuilder operationBuilder = new OperationBuilder(TemplateFormats.asciidoctor());
@Test
public void undocumentedRequestPartField() {
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() {
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() {
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-2023 the original author or authors.
* Copyright 2014-2025 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.
@@ -20,13 +20,15 @@ import java.io.IOException;
import java.util.Arrays;
import java.util.Collections;
import org.junit.Test;
import org.springframework.restdocs.AbstractSnippetTests;
import org.springframework.restdocs.operation.Operation;
import org.springframework.restdocs.templates.TemplateFormat;
import org.springframework.restdocs.snippet.SnippetException;
import org.springframework.restdocs.testfixtures.jupiter.AssertableSnippets;
import org.springframework.restdocs.testfixtures.jupiter.OperationBuilder;
import org.springframework.restdocs.testfixtures.jupiter.RenderedSnippetTest;
import org.springframework.restdocs.testfixtures.jupiter.SnippetTest;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.springframework.restdocs.payload.PayloadDocumentation.beneathPath;
import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath;
@@ -36,86 +38,110 @@ import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWit
* @author Mathieu Pousse
* @author Andy Wilkinson
*/
public class RequestPartFieldsSnippetTests extends AbstractSnippetTests {
public class RequestPartFieldsSnippetTests {
public RequestPartFieldsSnippetTests(String name, TemplateFormat templateFormat) {
super(name, templateFormat);
}
@Test
public void mapRequestPartFields() throws IOException {
@RenderedSnippetTest
void mapRequestPartFields(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
new RequestPartFieldsSnippet("one",
Arrays.asList(fieldWithPath("a.b").description("one"), fieldWithPath("a.c").description("two"),
fieldWithPath("a").description("three")))
.document(this.operationBuilder.request("http://localhost")
.document(operationBuilder.request("http://localhost")
.part("one", "{\"a\": {\"b\": 5, \"c\": \"charlie\"}}".getBytes())
.build());
assertThat(this.generatedSnippets.requestPartFields("one"))
.is(tableWithHeader("Path", "Type", "Description").row("`a.b`", "`Number`", "one")
.row("`a.c`", "`String`", "two")
.row("`a`", "`Object`", "three"));
assertThat(snippets.requestPartFields("one")).isTable((table) -> table.withHeader("Path", "Type", "Description")
.row("`a.b`", "`Number`", "one")
.row("`a.c`", "`String`", "two")
.row("`a`", "`Object`", "three"));
}
@Test
public void mapRequestPartSubsectionFields() throws IOException {
@RenderedSnippetTest
void mapRequestPartSubsectionFields(OperationBuilder operationBuilder, AssertableSnippets snippets)
throws IOException {
new RequestPartFieldsSnippet("one", beneathPath("a"),
Arrays.asList(fieldWithPath("b").description("one"), fieldWithPath("c").description("two")))
.document(this.operationBuilder.request("http://localhost")
.document(operationBuilder.request("http://localhost")
.part("one", "{\"a\": {\"b\": 5, \"c\": \"charlie\"}}".getBytes())
.build());
assertThat(this.generatedSnippets.snippet("request-part-one-fields-beneath-a"))
.is(tableWithHeader("Path", "Type", "Description").row("`b`", "`Number`", "one")
assertThat(snippets.requestPartFields("one", "beneath-a"))
.isTable((table) -> table.withHeader("Path", "Type", "Description")
.row("`b`", "`Number`", "one")
.row("`c`", "`String`", "two"));
}
@Test
public void multipleRequestParts() throws IOException {
Operation operation = this.operationBuilder.request("http://localhost")
@RenderedSnippetTest
void multipleRequestParts(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
Operation operation = operationBuilder.request("http://localhost")
.part("one", "{}".getBytes())
.and()
.part("two", "{}".getBytes())
.build();
new RequestPartFieldsSnippet("one", Collections.<FieldDescriptor>emptyList()).document(operation);
new RequestPartFieldsSnippet("two", Collections.<FieldDescriptor>emptyList()).document(operation);
assertThat(this.generatedSnippets.requestPartFields("one")).isNotNull();
assertThat(this.generatedSnippets.requestPartFields("two")).isNotNull();
assertThat(snippets.requestPartFields("one")).isNotNull();
assertThat(snippets.requestPartFields("two")).isNotNull();
}
@Test
public void allUndocumentedRequestPartFieldsCanBeIgnored() throws IOException {
new RequestPartFieldsSnippet("one", Arrays.asList(fieldWithPath("b").description("Field b")), true)
.document(this.operationBuilder.request("http://localhost")
.part("one", "{\"a\": 5, \"b\": 4}".getBytes())
.build());
assertThat(this.generatedSnippets.requestPartFields("one"))
.is(tableWithHeader("Path", "Type", "Description").row("`b`", "`Number`", "Field b"));
@RenderedSnippetTest
void allUndocumentedRequestPartFieldsCanBeIgnored(OperationBuilder operationBuilder, AssertableSnippets snippets)
throws IOException {
new RequestPartFieldsSnippet("one", Arrays.asList(fieldWithPath("b").description("Field b")), true).document(
operationBuilder.request("http://localhost").part("one", "{\"a\": 5, \"b\": 4}".getBytes()).build());
assertThat(snippets.requestPartFields("one"))
.isTable((table) -> table.withHeader("Path", "Type", "Description").row("`b`", "`Number`", "Field b"));
}
@Test
public void additionalDescriptors() throws IOException {
@RenderedSnippetTest
void additionalDescriptors(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
PayloadDocumentation
.requestPartFields("one", fieldWithPath("a.b").description("one"), fieldWithPath("a.c").description("two"))
.and(fieldWithPath("a").description("three"))
.document(this.operationBuilder.request("http://localhost")
.document(operationBuilder.request("http://localhost")
.part("one", "{\"a\": {\"b\": 5, \"c\": \"charlie\"}}".getBytes())
.build());
assertThat(this.generatedSnippets.requestPartFields("one"))
.is(tableWithHeader("Path", "Type", "Description").row("`a.b`", "`Number`", "one")
.row("`a.c`", "`String`", "two")
.row("`a`", "`Object`", "three"));
assertThat(snippets.requestPartFields("one")).isTable((table) -> table.withHeader("Path", "Type", "Description")
.row("`a.b`", "`Number`", "one")
.row("`a.c`", "`String`", "two")
.row("`a`", "`Object`", "three"));
}
@Test
public void prefixedAdditionalDescriptors() throws IOException {
@RenderedSnippetTest
void prefixedAdditionalDescriptors(OperationBuilder operationBuilder, AssertableSnippets snippets)
throws IOException {
PayloadDocumentation.requestPartFields("one", fieldWithPath("a").description("one"))
.andWithPrefix("a.", fieldWithPath("b").description("two"), fieldWithPath("c").description("three"))
.document(this.operationBuilder.request("http://localhost")
.document(operationBuilder.request("http://localhost")
.part("one", "{\"a\": {\"b\": 5, \"c\": \"charlie\"}}".getBytes())
.build());
assertThat(this.generatedSnippets.requestPartFields("one"))
.is(tableWithHeader("Path", "Type", "Description").row("`a`", "`Object`", "one")
.row("`a.b`", "`Number`", "two")
.row("`a.c`", "`String`", "three"));
assertThat(snippets.requestPartFields("one")).isTable((table) -> table.withHeader("Path", "Type", "Description")
.row("`a`", "`Object`", "one")
.row("`a.b`", "`Number`", "two")
.row("`a.c`", "`String`", "three"));
}
@SnippetTest
void undocumentedRequestPartField(OperationBuilder operationBuilder) {
assertThatExceptionOfType(SnippetException.class)
.isThrownBy(() -> new RequestPartFieldsSnippet("part", Collections.<FieldDescriptor>emptyList())
.document(operationBuilder.request("http://localhost").part("part", "{\"a\": 5}".getBytes()).build()))
.withMessageStartingWith("The following parts of the payload were not documented:");
}
@SnippetTest
void missingRequestPartField(OperationBuilder operationBuilder) {
assertThatExceptionOfType(SnippetException.class)
.isThrownBy(() -> new RequestPartFieldsSnippet("part", Arrays.asList(fieldWithPath("b").description("one")))
.document(operationBuilder.request("http://localhost").part("part", "{\"a\": 5}".getBytes()).build()))
.withMessageStartingWith("The following parts of the payload were not documented:");
}
@SnippetTest
void missingRequestPart(OperationBuilder operationBuilder) {
assertThatExceptionOfType(SnippetException.class).isThrownBy(
() -> new RequestPartFieldsSnippet("another", Arrays.asList(fieldWithPath("a.b").description("one")))
.document(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-2023 the original author or authors.
* Copyright 2014-2025 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,19 +18,14 @@ package org.springframework.restdocs.payload;
import java.io.IOException;
import org.junit.Test;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.restdocs.AbstractSnippetTests;
import org.springframework.restdocs.templates.TemplateEngine;
import org.springframework.restdocs.templates.TemplateFormat;
import org.springframework.restdocs.templates.TemplateResourceResolver;
import org.springframework.restdocs.templates.mustache.MustacheTemplateEngine;
import org.springframework.restdocs.testfixtures.jupiter.AssertableSnippets;
import org.springframework.restdocs.testfixtures.jupiter.OperationBuilder;
import org.springframework.restdocs.testfixtures.jupiter.RenderedSnippetTest;
import org.springframework.restdocs.testfixtures.jupiter.SnippetTemplate;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
import static org.springframework.restdocs.payload.PayloadDocumentation.beneathPath;
import static org.springframework.restdocs.payload.PayloadDocumentation.responseBody;
import static org.springframework.restdocs.snippet.Attributes.attributes;
@@ -41,77 +36,72 @@ import static org.springframework.restdocs.snippet.Attributes.key;
*
* @author Andy Wilkinson
*/
public class ResponseBodySnippetTests extends AbstractSnippetTests {
class ResponseBodySnippetTests {
public ResponseBodySnippetTests(String name, TemplateFormat templateFormat) {
super(name, templateFormat);
@RenderedSnippetTest
void responseWithBody(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
new ResponseBodySnippet().document(operationBuilder.response().content("some content").build());
assertThat(snippets.responseBody())
.isCodeBlock((codeBlock) -> codeBlock.withOptions("nowrap").content("some content"));
}
@Test
public void responseWithBody() throws IOException {
new ResponseBodySnippet().document(this.operationBuilder.response().content("some content").build());
assertThat(this.generatedSnippets.snippet("response-body"))
.is(codeBlock(null, "nowrap").withContent("some content"));
@RenderedSnippetTest
void responseWithNoBody(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
new ResponseBodySnippet().document(operationBuilder.response().build());
assertThat(snippets.responseBody()).isCodeBlock((codeBlock) -> codeBlock.withOptions("nowrap").content(""));
}
@Test
public void responseWithNoBody() throws IOException {
new ResponseBodySnippet().document(this.operationBuilder.response().build());
assertThat(this.generatedSnippets.snippet("response-body")).is(codeBlock(null, "nowrap").withContent(""));
@RenderedSnippetTest
void responseWithJsonMediaType(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
new ResponseBodySnippet().document(
operationBuilder.response().header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE).build());
assertThat(snippets.responseBody())
.isCodeBlock((codeBlock) -> codeBlock.withLanguageAndOptions("json", "nowrap").content(""));
}
@Test
public void responseWithJsonMediaType() throws IOException {
new ResponseBodySnippet().document(this.operationBuilder.response()
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
.build());
assertThat(this.generatedSnippets.snippet("response-body")).is(codeBlock("json", "nowrap").withContent(""));
}
@Test
public void responseWithJsonSubtypeMediaType() throws IOException {
new ResponseBodySnippet().document(this.operationBuilder.response()
@RenderedSnippetTest
void responseWithJsonSubtypeMediaType(OperationBuilder operationBuilder, AssertableSnippets snippets)
throws IOException {
new ResponseBodySnippet().document(operationBuilder.response()
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_PROBLEM_JSON_VALUE)
.build());
assertThat(this.generatedSnippets.snippet("response-body")).is(codeBlock("json", "nowrap").withContent(""));
assertThat(snippets.responseBody())
.isCodeBlock((codeBlock) -> codeBlock.withLanguageAndOptions("json", "nowrap").content(""));
}
@Test
public void responseWithXmlMediaType() throws IOException {
new ResponseBodySnippet().document(this.operationBuilder.response()
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_XML_VALUE)
.build());
assertThat(this.generatedSnippets.snippet("response-body")).is(codeBlock("xml", "nowrap").withContent(""));
@RenderedSnippetTest
void responseWithXmlMediaType(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
new ResponseBodySnippet().document(
operationBuilder.response().header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_XML_VALUE).build());
assertThat(snippets.responseBody())
.isCodeBlock((codeBlock) -> codeBlock.withLanguageAndOptions("xml", "nowrap").content(""));
}
@Test
public void responseWithXmlSubtypeMediaType() throws IOException {
new ResponseBodySnippet().document(this.operationBuilder.response()
@RenderedSnippetTest
void responseWithXmlSubtypeMediaType(OperationBuilder operationBuilder, AssertableSnippets snippets)
throws IOException {
new ResponseBodySnippet().document(operationBuilder.response()
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_ATOM_XML_VALUE)
.build());
assertThat(this.generatedSnippets.snippet("response-body")).is(codeBlock("xml", "nowrap").withContent(""));
assertThat(snippets.responseBody())
.isCodeBlock((codeBlock) -> codeBlock.withLanguageAndOptions("xml", "nowrap").content(""));
}
@Test
public void subsectionOfResponseBody() throws IOException {
@RenderedSnippetTest
void subsectionOfResponseBody(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
responseBody(beneathPath("a.b"))
.document(this.operationBuilder.response().content("{\"a\":{\"b\":{\"c\":5}}}").build());
assertThat(this.generatedSnippets.snippet("response-body-beneath-a.b"))
.is(codeBlock(null, "nowrap").withContent("{\"c\":5}"));
.document(operationBuilder.response().content("{\"a\":{\"b\":{\"c\":5}}}").build());
assertThat(snippets.responseBody("beneath-a.b"))
.isCodeBlock((codeBlock) -> codeBlock.withOptions("nowrap").content("{\"c\":5}"));
}
@Test
public void customSnippetAttributes() throws IOException {
TemplateResourceResolver resolver = mock(TemplateResourceResolver.class);
given(resolver.resolveTemplateResource("response-body"))
.willReturn(snippetResource("response-body-with-language"));
new ResponseBodySnippet(attributes(key("language").value("json"))).document(
this.operationBuilder.attribute(TemplateEngine.class.getName(), new MustacheTemplateEngine(resolver))
.response()
.content("{\"a\":\"alpha\"}")
.build());
assertThat(this.generatedSnippets.snippet("response-body"))
.is(codeBlock("json", "nowrap").withContent("{\"a\":\"alpha\"}"));
@RenderedSnippetTest
@SnippetTemplate(snippet = "response-body", template = "response-body-with-language")
void customSnippetAttributes(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
new ResponseBodySnippet(attributes(key("language").value("json")))
.document(operationBuilder.response().content("{\"a\":\"alpha\"}").build());
assertThat(snippets.responseBody()).isCodeBlock(
(codeBlock) -> codeBlock.withLanguageAndOptions("json", "nowrap").content("{\"a\":\"alpha\"}"));
}
}

View File

@@ -1,175 +0,0 @@
/*
* Copyright 2014-2023 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.payload;
import java.util.Arrays;
import java.util.Collections;
import org.junit.Rule;
import org.junit.Test;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.restdocs.snippet.SnippetException;
import org.springframework.restdocs.templates.TemplateFormats;
import org.springframework.restdocs.testfixtures.OperationBuilder;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
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 OperationBuilder operationBuilder = new OperationBuilder(TemplateFormats.asciidoctor());
@Test
public void attemptToDocumentFieldsWithNoResponseBody() {
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() {
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() {
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() {
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() {
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() {
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() {
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() {
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() {
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() {
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() {
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() {
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

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2024 the original author or authors.
* Copyright 2014-2025 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,21 +18,19 @@ package org.springframework.restdocs.payload;
import java.io.IOException;
import java.util.Arrays;
import org.junit.Test;
import java.util.Collections;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.restdocs.AbstractSnippetTests;
import org.springframework.restdocs.templates.TemplateEngine;
import org.springframework.restdocs.templates.TemplateFormat;
import org.springframework.restdocs.templates.TemplateFormats;
import org.springframework.restdocs.templates.TemplateResourceResolver;
import org.springframework.restdocs.templates.mustache.MustacheTemplateEngine;
import org.springframework.restdocs.snippet.SnippetException;
import org.springframework.restdocs.testfixtures.jupiter.AssertableSnippets;
import org.springframework.restdocs.testfixtures.jupiter.OperationBuilder;
import org.springframework.restdocs.testfixtures.jupiter.RenderedSnippetTest;
import org.springframework.restdocs.testfixtures.jupiter.SnippetTemplate;
import org.springframework.restdocs.testfixtures.jupiter.SnippetTest;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.springframework.restdocs.payload.PayloadDocumentation.beneathPath;
import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath;
import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields;
@@ -45,355 +43,486 @@ import static org.springframework.restdocs.snippet.Attributes.key;
* @author Andy Wilkinson
* @author Sungjun Lee
*/
public class ResponseFieldsSnippetTests extends AbstractSnippetTests {
public class ResponseFieldsSnippetTests {
public ResponseFieldsSnippetTests(String name, TemplateFormat templateFormat) {
super(name, templateFormat);
}
@Test
public void mapResponseWithFields() throws IOException {
@RenderedSnippetTest
void mapResponseWithFields(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
new ResponseFieldsSnippet(Arrays.asList(fieldWithPath("id").description("one"),
fieldWithPath("date").description("two"), fieldWithPath("assets").description("three"),
fieldWithPath("assets[]").description("four"), fieldWithPath("assets[].id").description("five"),
fieldWithPath("assets[].name").description("six")))
.document(this.operationBuilder.response()
.document(operationBuilder.response()
.content("{\"id\": 67,\"date\": \"2015-01-20\",\"assets\":" + " [{\"id\":356,\"name\": \"sample\"}]}")
.build());
assertThat(this.generatedSnippets.responseFields())
.is(tableWithHeader("Path", "Type", "Description").row("`id`", "`Number`", "one")
.row("`date`", "`String`", "two")
.row("`assets`", "`Array`", "three")
.row("`assets[]`", "`Array`", "four")
.row("`assets[].id`", "`Number`", "five")
.row("`assets[].name`", "`String`", "six"));
assertThat(snippets.responseFields()).isTable((table) -> table.withHeader("Path", "Type", "Description")
.row("`id`", "`Number`", "one")
.row("`date`", "`String`", "two")
.row("`assets`", "`Array`", "three")
.row("`assets[]`", "`Array`", "four")
.row("`assets[].id`", "`Number`", "five")
.row("`assets[].name`", "`String`", "six"));
}
@Test
public void mapResponseWithNullField() throws IOException {
@RenderedSnippetTest
void mapResponseWithNullField(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
new ResponseFieldsSnippet(Arrays.asList(fieldWithPath("a.b").description("one")))
.document(this.operationBuilder.response().content("{\"a\": {\"b\": null}}").build());
assertThat(this.generatedSnippets.responseFields())
.is(tableWithHeader("Path", "Type", "Description").row("`a.b`", "`Null`", "one"));
.document(operationBuilder.response().content("{\"a\": {\"b\": null}}").build());
assertThat(snippets.responseFields())
.isTable((table) -> table.withHeader("Path", "Type", "Description").row("`a.b`", "`Null`", "one"));
}
@Test
public void subsectionOfMapResponse() throws IOException {
@RenderedSnippetTest
void subsectionOfMapResponse(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
responseFields(beneathPath("a"), fieldWithPath("b").description("one"), fieldWithPath("c").description("two"))
.document(this.operationBuilder.response().content("{\"a\": {\"b\": 5, \"c\": \"charlie\"}}").build());
assertThat(this.generatedSnippets.snippet("response-fields-beneath-a"))
.is(tableWithHeader("Path", "Type", "Description").row("`b`", "`Number`", "one")
.document(operationBuilder.response().content("{\"a\": {\"b\": 5, \"c\": \"charlie\"}}").build());
assertThat(snippets.responseFields("beneath-a"))
.isTable((table) -> table.withHeader("Path", "Type", "Description")
.row("`b`", "`Number`", "one")
.row("`c`", "`String`", "two"));
}
@Test
public void subsectionOfMapResponseBeneathAnArray() throws IOException {
@RenderedSnippetTest
void subsectionOfMapResponseBeneathAnArray(OperationBuilder operationBuilder, AssertableSnippets snippets)
throws IOException {
responseFields(beneathPath("a.b.[]"), fieldWithPath("c").description("one"),
fieldWithPath("d.[].e").description("two"))
.document(this.operationBuilder.response()
.document(operationBuilder.response()
.content("{\"a\": {\"b\": [{\"c\": 1, \"d\": [{\"e\": 5}]}, {\"c\": 3, \"d\": [{\"e\": 4}]}]}}")
.build());
assertThat(this.generatedSnippets.snippet("response-fields-beneath-a.b.[]"))
.is(tableWithHeader("Path", "Type", "Description").row("`c`", "`Number`", "one")
assertThat(snippets.responseFields("beneath-a.b.[]"))
.isTable((table) -> table.withHeader("Path", "Type", "Description")
.row("`c`", "`Number`", "one")
.row("`d.[].e`", "`Number`", "two"));
}
@Test
public void subsectionOfMapResponseWithCommonsPrefix() throws IOException {
@RenderedSnippetTest
void subsectionOfMapResponseWithCommonsPrefix(OperationBuilder operationBuilder, AssertableSnippets snippets)
throws IOException {
responseFields(beneathPath("a")).andWithPrefix("b.", fieldWithPath("c").description("two"))
.document(this.operationBuilder.response().content("{\"a\": {\"b\": {\"c\": \"charlie\"}}}").build());
assertThat(this.generatedSnippets.snippet("response-fields-beneath-a"))
.is(tableWithHeader("Path", "Type", "Description").row("`b.c`", "`String`", "two"));
.document(operationBuilder.response().content("{\"a\": {\"b\": {\"c\": \"charlie\"}}}").build());
assertThat(snippets.responseFields("beneath-a"))
.isTable((table) -> table.withHeader("Path", "Type", "Description").row("`b.c`", "`String`", "two"));
}
@Test
public void arrayResponseWithFields() throws IOException {
@RenderedSnippetTest
void arrayResponseWithFields(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
new ResponseFieldsSnippet(Arrays.asList(fieldWithPath("[]a.b").description("one"),
fieldWithPath("[]a.c").description("two"), fieldWithPath("[]a").description("three")))
.document(this.operationBuilder.response()
.document(operationBuilder.response()
.content("[{\"a\": {\"b\": 5, \"c\":\"charlie\"}}," + "{\"a\": {\"b\": 4, \"c\":\"chalk\"}}]")
.build());
assertThat(this.generatedSnippets.responseFields())
.is(tableWithHeader("Path", "Type", "Description").row("`[]a.b`", "`Number`", "one")
.row("`[]a.c`", "`String`", "two")
.row("`[]a`", "`Object`", "three"));
assertThat(snippets.responseFields()).isTable((table) -> table.withHeader("Path", "Type", "Description")
.row("`[]a.b`", "`Number`", "one")
.row("`[]a.c`", "`String`", "two")
.row("`[]a`", "`Object`", "three"));
}
@Test
public void arrayResponseWithAlwaysNullField() throws IOException {
new ResponseFieldsSnippet(Arrays.asList(fieldWithPath("[]a.b").description("one")))
.document(this.operationBuilder.response()
.content("[{\"a\": {\"b\": null}}," + "{\"a\": {\"b\": null}}]")
.build());
assertThat(this.generatedSnippets.responseFields())
.is(tableWithHeader("Path", "Type", "Description").row("`[]a.b`", "`Null`", "one"));
@RenderedSnippetTest
void arrayResponseWithAlwaysNullField(OperationBuilder operationBuilder, AssertableSnippets snippets)
throws IOException {
new ResponseFieldsSnippet(Arrays.asList(fieldWithPath("[]a.b").description("one"))).document(
operationBuilder.response().content("[{\"a\": {\"b\": null}}," + "{\"a\": {\"b\": null}}]").build());
assertThat(snippets.responseFields())
.isTable((table) -> table.withHeader("Path", "Type", "Description").row("`[]a.b`", "`Null`", "one"));
}
@Test
public void arrayResponse() throws IOException {
@RenderedSnippetTest
void arrayResponse(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
new ResponseFieldsSnippet(Arrays.asList(fieldWithPath("[]").description("one")))
.document(this.operationBuilder.response().content("[\"a\", \"b\", \"c\"]").build());
assertThat(this.generatedSnippets.responseFields())
.is(tableWithHeader("Path", "Type", "Description").row("`[]`", "`Array`", "one"));
.document(operationBuilder.response().content("[\"a\", \"b\", \"c\"]").build());
assertThat(snippets.responseFields())
.isTable((table) -> table.withHeader("Path", "Type", "Description").row("`[]`", "`Array`", "one"));
}
@Test
public void ignoredResponseField() throws IOException {
@RenderedSnippetTest
void ignoredResponseField(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
new ResponseFieldsSnippet(
Arrays.asList(fieldWithPath("a").ignored(), fieldWithPath("b").description("Field b")))
.document(this.operationBuilder.response().content("{\"a\": 5, \"b\": 4}").build());
assertThat(this.generatedSnippets.responseFields())
.is(tableWithHeader("Path", "Type", "Description").row("`b`", "`Number`", "Field b"));
.document(operationBuilder.response().content("{\"a\": 5, \"b\": 4}").build());
assertThat(snippets.responseFields())
.isTable((table) -> table.withHeader("Path", "Type", "Description").row("`b`", "`Number`", "Field b"));
}
@Test
public void allUndocumentedFieldsCanBeIgnored() throws IOException {
@RenderedSnippetTest
void allUndocumentedFieldsCanBeIgnored(OperationBuilder operationBuilder, AssertableSnippets snippets)
throws IOException {
new ResponseFieldsSnippet(Arrays.asList(fieldWithPath("b").description("Field b")), true)
.document(this.operationBuilder.response().content("{\"a\": 5, \"b\": 4}").build());
assertThat(this.generatedSnippets.responseFields())
.is(tableWithHeader("Path", "Type", "Description").row("`b`", "`Number`", "Field b"));
.document(operationBuilder.response().content("{\"a\": 5, \"b\": 4}").build());
assertThat(snippets.responseFields())
.isTable((table) -> table.withHeader("Path", "Type", "Description").row("`b`", "`Number`", "Field b"));
}
@Test
public void allUndocumentedFieldsContinueToBeIgnoredAfterAddingDescriptors() throws IOException {
@RenderedSnippetTest
void allUndocumentedFieldsContinueToBeIgnoredAfterAddingDescriptors(OperationBuilder operationBuilder,
AssertableSnippets snippets) throws IOException {
new ResponseFieldsSnippet(Arrays.asList(fieldWithPath("b").description("Field b")), true)
.andWithPrefix("c.", fieldWithPath("d").description("Field d"))
.document(this.operationBuilder.response().content("{\"a\":5,\"b\":4,\"c\":{\"d\": 3}}").build());
assertThat(this.generatedSnippets.responseFields())
.is(tableWithHeader("Path", "Type", "Description").row("`b`", "`Number`", "Field b")
.row("`c.d`", "`Number`", "Field d"));
.document(operationBuilder.response().content("{\"a\":5,\"b\":4,\"c\":{\"d\": 3}}").build());
assertThat(snippets.responseFields()).isTable((table) -> table.withHeader("Path", "Type", "Description")
.row("`b`", "`Number`", "Field b")
.row("`c.d`", "`Number`", "Field d"));
}
@Test
public void responseFieldsWithCustomAttributes() throws IOException {
TemplateResourceResolver resolver = mock(TemplateResourceResolver.class);
given(resolver.resolveTemplateResource("response-fields"))
.willReturn(snippetResource("response-fields-with-title"));
@RenderedSnippetTest
@SnippetTemplate(snippet = "response-fields", template = "response-fields-with-title")
void responseFieldsWithCustomAttributes(OperationBuilder operationBuilder, AssertableSnippets snippets)
throws IOException {
new ResponseFieldsSnippet(Arrays.asList(fieldWithPath("a").description("one")),
attributes(key("title").value("Custom title")))
.document(this.operationBuilder
.attribute(TemplateEngine.class.getName(), new MustacheTemplateEngine(resolver))
.response()
.content("{\"a\": \"foo\"}")
.build());
assertThat(this.generatedSnippets.responseFields()).contains("Custom title");
.document(operationBuilder.response().content("{\"a\": \"foo\"}").build());
assertThat(snippets.responseFields()).contains("Custom title");
}
@Test
public void missingOptionalResponseField() throws IOException {
@RenderedSnippetTest
void missingOptionalResponseField(OperationBuilder operationBuilder, AssertableSnippets snippets)
throws IOException {
new ResponseFieldsSnippet(
Arrays.asList(fieldWithPath("a.b").description("one").type(JsonFieldType.STRING).optional()))
.document(this.operationBuilder.response().content("{}").build());
assertThat(this.generatedSnippets.responseFields())
.is(tableWithHeader("Path", "Type", "Description").row("`a.b`", "`String`", "one"));
.document(operationBuilder.response().content("{}").build());
assertThat(snippets.responseFields())
.isTable((table) -> table.withHeader("Path", "Type", "Description").row("`a.b`", "`String`", "one"));
}
@Test
public void missingIgnoredOptionalResponseFieldDoesNotRequireAType() throws IOException {
@RenderedSnippetTest
void missingIgnoredOptionalResponseFieldDoesNotRequireAType(OperationBuilder operationBuilder,
AssertableSnippets snippets) throws IOException {
new ResponseFieldsSnippet(Arrays.asList(fieldWithPath("a.b").description("one").ignored().optional()))
.document(this.operationBuilder.response().content("{}").build());
assertThat(this.generatedSnippets.responseFields()).is(tableWithHeader("Path", "Type", "Description"));
.document(operationBuilder.response().content("{}").build());
assertThat(snippets.responseFields()).isTable((table) -> table.withHeader("Path", "Type", "Description"));
}
@Test
public void presentOptionalResponseField() throws IOException {
@RenderedSnippetTest
void presentOptionalResponseField(OperationBuilder operationBuilder, AssertableSnippets snippets)
throws IOException {
new ResponseFieldsSnippet(
Arrays.asList(fieldWithPath("a.b").description("one").type(JsonFieldType.STRING).optional()))
.document(this.operationBuilder.response().content("{\"a\": { \"b\": \"bravo\"}}").build());
assertThat(this.generatedSnippets.responseFields())
.is(tableWithHeader("Path", "Type", "Description").row("`a.b`", "`String`", "one"));
.document(operationBuilder.response().content("{\"a\": { \"b\": \"bravo\"}}").build());
assertThat(snippets.responseFields())
.isTable((table) -> table.withHeader("Path", "Type", "Description").row("`a.b`", "`String`", "one"));
}
@Test
public void responseFieldsWithCustomDescriptorAttributes() throws IOException {
TemplateResourceResolver resolver = mock(TemplateResourceResolver.class);
given(resolver.resolveTemplateResource("response-fields"))
.willReturn(snippetResource("response-fields-with-extra-column"));
@RenderedSnippetTest
@SnippetTemplate(snippet = "response-fields", template = "response-fields-with-extra-column")
void responseFieldsWithCustomDescriptorAttributes(OperationBuilder operationBuilder, AssertableSnippets snippets)
throws IOException {
new ResponseFieldsSnippet(
Arrays.asList(fieldWithPath("a.b").description("one").attributes(key("foo").value("alpha")),
fieldWithPath("a.c").description("two").attributes(key("foo").value("bravo")),
fieldWithPath("a").description("three").attributes(key("foo").value("charlie"))))
.document(this.operationBuilder
.attribute(TemplateEngine.class.getName(), new MustacheTemplateEngine(resolver))
.response()
.content("{\"a\": {\"b\": 5, \"c\": \"charlie\"}}")
.build());
assertThat(this.generatedSnippets.responseFields())
.is(tableWithHeader("Path", "Type", "Description", "Foo").row("a.b", "Number", "one", "alpha")
.row("a.c", "String", "two", "bravo")
.row("a", "Object", "three", "charlie"));
.document(operationBuilder.response().content("{\"a\": {\"b\": 5, \"c\": \"charlie\"}}").build());
assertThat(snippets.responseFields()).isTable((table) -> table.withHeader("Path", "Type", "Description", "Foo")
.row("a.b", "Number", "one", "alpha")
.row("a.c", "String", "two", "bravo")
.row("a", "Object", "three", "charlie"));
}
@Test
public void fieldWithExplicitExactlyMatchingType() throws IOException {
@RenderedSnippetTest
void fieldWithExplicitExactlyMatchingType(OperationBuilder operationBuilder, AssertableSnippets snippets)
throws IOException {
new ResponseFieldsSnippet(Arrays.asList(fieldWithPath("a").description("one").type(JsonFieldType.NUMBER)))
.document(this.operationBuilder.response().content("{\"a\": 5 }").build());
assertThat(this.generatedSnippets.responseFields())
.is(tableWithHeader("Path", "Type", "Description").row("`a`", "`Number`", "one"));
.document(operationBuilder.response().content("{\"a\": 5 }").build());
assertThat(snippets.responseFields())
.isTable((table) -> table.withHeader("Path", "Type", "Description").row("`a`", "`Number`", "one"));
}
@Test
public void fieldWithExplicitVariesType() throws IOException {
@RenderedSnippetTest
void fieldWithExplicitVariesType(OperationBuilder operationBuilder, AssertableSnippets snippets)
throws IOException {
new ResponseFieldsSnippet(Arrays.asList(fieldWithPath("a").description("one").type(JsonFieldType.VARIES)))
.document(this.operationBuilder.response().content("{\"a\": 5 }").build());
assertThat(this.generatedSnippets.responseFields())
.is(tableWithHeader("Path", "Type", "Description").row("`a`", "`Varies`", "one"));
.document(operationBuilder.response().content("{\"a\": 5 }").build());
assertThat(snippets.responseFields())
.isTable((table) -> table.withHeader("Path", "Type", "Description").row("`a`", "`Varies`", "one"));
}
@Test
public void applicationXmlResponseFields() throws IOException {
xmlResponseFields(MediaType.APPLICATION_XML);
@RenderedSnippetTest
void applicationXmlResponseFields(OperationBuilder operationBuilder, AssertableSnippets snippets)
throws IOException {
xmlResponseFields(MediaType.APPLICATION_XML, operationBuilder, snippets);
}
@Test
public void textXmlResponseFields() throws IOException {
xmlResponseFields(MediaType.TEXT_XML);
@RenderedSnippetTest
void textXmlResponseFields(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
xmlResponseFields(MediaType.TEXT_XML, operationBuilder, snippets);
}
@Test
public void customXmlResponseFields() throws IOException {
xmlResponseFields(MediaType.parseMediaType("application/vnd.com.example+xml"));
@RenderedSnippetTest
void customXmlResponseFields(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
xmlResponseFields(MediaType.parseMediaType("application/vnd.com.example+xml"), operationBuilder, snippets);
}
private void xmlResponseFields(MediaType contentType) throws IOException {
private void xmlResponseFields(MediaType contentType, OperationBuilder operationBuilder,
AssertableSnippets snippets) throws IOException {
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(this.operationBuilder.response()
.document(operationBuilder.response()
.content("<a><b>5</b><c>charlie</c></a>")
.header(HttpHeaders.CONTENT_TYPE, contentType.toString())
.build());
assertThat(this.generatedSnippets.responseFields())
.is(tableWithHeader("Path", "Type", "Description").row("`a/b`", "`b`", "one")
.row("`a/c`", "`c`", "two")
.row("`a`", "`a`", "three"));
assertThat(snippets.responseFields()).isTable((table) -> table.withHeader("Path", "Type", "Description")
.row("`a/b`", "`b`", "one")
.row("`a/c`", "`c`", "two")
.row("`a`", "`a`", "three"));
}
@Test
public void xmlAttribute() throws IOException {
@RenderedSnippetTest
void xmlAttribute(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
new ResponseFieldsSnippet(Arrays.asList(fieldWithPath("a").description("one").type("b"),
fieldWithPath("a/@id").description("two").type("c")))
.document(this.operationBuilder.response()
.document(operationBuilder.response()
.content("<a id=\"1\">foo</a>")
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_XML_VALUE)
.build());
assertThat(this.generatedSnippets.responseFields())
.is(tableWithHeader("Path", "Type", "Description").row("`a`", "`b`", "one").row("`a/@id`", "`c`", "two"));
assertThat(snippets.responseFields()).isTable((table) -> table.withHeader("Path", "Type", "Description")
.row("`a`", "`b`", "one")
.row("`a/@id`", "`c`", "two"));
}
@Test
public void missingOptionalXmlAttribute() throws IOException {
@RenderedSnippetTest
void missingOptionalXmlAttribute(OperationBuilder operationBuilder, AssertableSnippets snippets)
throws IOException {
new ResponseFieldsSnippet(Arrays.asList(fieldWithPath("a").description("one").type("b"),
fieldWithPath("a/@id").description("two").type("c").optional()))
.document(this.operationBuilder.response()
.document(operationBuilder.response()
.content("<a>foo</a>")
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_XML_VALUE)
.build());
assertThat(this.generatedSnippets.responseFields())
.is(tableWithHeader("Path", "Type", "Description").row("`a`", "`b`", "one").row("`a/@id`", "`c`", "two"));
assertThat(snippets.responseFields()).isTable((table) -> table.withHeader("Path", "Type", "Description")
.row("`a`", "`b`", "one")
.row("`a/@id`", "`c`", "two"));
}
@Test
public void undocumentedAttributeDoesNotCauseFailure() throws IOException {
@RenderedSnippetTest
void undocumentedAttributeDoesNotCauseFailure(OperationBuilder operationBuilder, AssertableSnippets snippets)
throws IOException {
new ResponseFieldsSnippet(Arrays.asList(fieldWithPath("a").description("one").type("a")))
.document(this.operationBuilder.response()
.document(operationBuilder.response()
.content("<a id=\"foo\">bar</a>")
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_XML_VALUE)
.build());
assertThat(this.generatedSnippets.responseFields())
.is(tableWithHeader("Path", "Type", "Description").row("`a`", "`a`", "one"));
assertThat(snippets.responseFields())
.isTable((table) -> table.withHeader("Path", "Type", "Description").row("`a`", "`a`", "one"));
}
@Test
public void additionalDescriptors() throws IOException {
@RenderedSnippetTest
void additionalDescriptors(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
PayloadDocumentation
.responseFields(fieldWithPath("id").description("one"), fieldWithPath("date").description("two"),
fieldWithPath("assets").description("three"))
.and(fieldWithPath("assets[]").description("four"), fieldWithPath("assets[].id").description("five"),
fieldWithPath("assets[].name").description("six"))
.document(this.operationBuilder.response()
.document(operationBuilder.response()
.content("{\"id\": 67,\"date\": \"2015-01-20\",\"assets\":" + " [{\"id\":356,\"name\": \"sample\"}]}")
.build());
assertThat(this.generatedSnippets.responseFields())
.is(tableWithHeader("Path", "Type", "Description").row("`id`", "`Number`", "one")
.row("`date`", "`String`", "two")
.row("`assets`", "`Array`", "three")
.row("`assets[]`", "`Array`", "four")
.row("`assets[].id`", "`Number`", "five")
.row("`assets[].name`", "`String`", "six"));
assertThat(snippets.responseFields()).isTable((table) -> table.withHeader("Path", "Type", "Description")
.row("`id`", "`Number`", "one")
.row("`date`", "`String`", "two")
.row("`assets`", "`Array`", "three")
.row("`assets[]`", "`Array`", "four")
.row("`assets[].id`", "`Number`", "five")
.row("`assets[].name`", "`String`", "six"));
}
@Test
public void prefixedAdditionalDescriptors() throws IOException {
@RenderedSnippetTest
void prefixedAdditionalDescriptors(OperationBuilder operationBuilder, AssertableSnippets snippets)
throws IOException {
PayloadDocumentation.responseFields(fieldWithPath("a").description("one"))
.andWithPrefix("a.", fieldWithPath("b").description("two"), fieldWithPath("c").description("three"))
.document(this.operationBuilder.response().content("{\"a\": {\"b\": 5, \"c\": \"charlie\"}}").build());
assertThat(this.generatedSnippets.responseFields())
.is(tableWithHeader("Path", "Type", "Description").row("`a`", "`Object`", "one")
.row("`a.b`", "`Number`", "two")
.row("`a.c`", "`String`", "three"));
.document(operationBuilder.response().content("{\"a\": {\"b\": 5, \"c\": \"charlie\"}}").build());
assertThat(snippets.responseFields()).isTable((table) -> table.withHeader("Path", "Type", "Description")
.row("`a`", "`Object`", "one")
.row("`a.b`", "`Number`", "two")
.row("`a.c`", "`String`", "three"));
}
@Test
public void responseWithFieldsWithEscapedContent() throws IOException {
@RenderedSnippetTest
void responseWithFieldsWithEscapedContent(OperationBuilder operationBuilder, AssertableSnippets snippets)
throws IOException {
new ResponseFieldsSnippet(Arrays.asList(fieldWithPath("Foo|Bar").type("one|two").description("three|four")))
.document(this.operationBuilder.response().content("{\"Foo|Bar\": 5}").build());
assertThat(this.generatedSnippets.responseFields()).is(tableWithHeader("Path", "Type", "Description")
.row(escapeIfNecessary("`Foo|Bar`"), escapeIfNecessary("`one|two`"), escapeIfNecessary("three|four")));
.document(operationBuilder.response().content("{\"Foo|Bar\": 5}").build());
assertThat(snippets.responseFields()).isTable(
(table) -> table.withHeader("Path", "Type", "Description").row("`Foo|Bar`", "`one|two`", "three|four"));
}
@Test
public void mapResponseWithVaryingKeysMatchedUsingWildcard() throws IOException {
@RenderedSnippetTest
void mapResponseWithVaryingKeysMatchedUsingWildcard(OperationBuilder operationBuilder, AssertableSnippets snippets)
throws IOException {
new ResponseFieldsSnippet(Arrays.asList(fieldWithPath("things.*.size").description("one"),
fieldWithPath("things.*.type").description("two")))
.document(this.operationBuilder.response()
.document(operationBuilder.response()
.content("{\"things\": {\"12abf\": {\"type\":" + "\"Whale\", \"size\": \"HUGE\"},"
+ "\"gzM33\" : {\"type\": \"Screw\"," + "\"size\": \"SMALL\"}}}")
.build());
assertThat(this.generatedSnippets.responseFields())
.is(tableWithHeader("Path", "Type", "Description").row("`things.*.size`", "`String`", "one")
.row("`things.*.type`", "`String`", "two"));
assertThat(snippets.responseFields()).isTable((table) -> table.withHeader("Path", "Type", "Description")
.row("`things.*.size`", "`String`", "one")
.row("`things.*.type`", "`String`", "two"));
}
@Test
public void responseWithArrayContainingFieldThatIsSometimesNull() throws IOException {
@RenderedSnippetTest
void responseWithArrayContainingFieldThatIsSometimesNull(OperationBuilder operationBuilder,
AssertableSnippets snippets) throws IOException {
new ResponseFieldsSnippet(
Arrays.asList(fieldWithPath("assets[].name").description("one").type(JsonFieldType.STRING).optional()))
.document(this.operationBuilder.response()
.document(operationBuilder.response()
.content("{\"assets\": [" + "{\"name\": \"sample1\"}, " + "{\"name\": null}, "
+ "{\"name\": \"sample2\"}]}")
.build());
assertThat(this.generatedSnippets.responseFields())
.is(tableWithHeader("Path", "Type", "Description").row("`assets[].name`", "`String`", "one"));
assertThat(snippets.responseFields()).isTable(
(table) -> table.withHeader("Path", "Type", "Description").row("`assets[].name`", "`String`", "one"));
}
@Test
public void optionalFieldBeneathArrayThatIsSometimesAbsent() throws IOException {
@RenderedSnippetTest
void optionalFieldBeneathArrayThatIsSometimesAbsent(OperationBuilder operationBuilder, AssertableSnippets snippets)
throws IOException {
new ResponseFieldsSnippet(
Arrays.asList(fieldWithPath("a[].b").description("one").type(JsonFieldType.NUMBER).optional(),
fieldWithPath("a[].c").description("two").type(JsonFieldType.NUMBER)))
.document(this.operationBuilder.response()
.document(operationBuilder.response()
.content("{\"a\":[{\"b\": 1,\"c\": 2}, " + "{\"c\": 2}, {\"b\": 1,\"c\": 2}]}")
.build());
assertThat(this.generatedSnippets.responseFields())
.is(tableWithHeader("Path", "Type", "Description").row("`a[].b`", "`Number`", "one")
.row("`a[].c`", "`Number`", "two"));
assertThat(snippets.responseFields()).isTable((table) -> table.withHeader("Path", "Type", "Description")
.row("`a[].b`", "`Number`", "one")
.row("`a[].c`", "`Number`", "two"));
}
@Test
public void typeDeterminationDoesNotSetTypeOnDescriptor() throws IOException {
@RenderedSnippetTest
void typeDeterminationDoesNotSetTypeOnDescriptor(OperationBuilder operationBuilder, AssertableSnippets snippets)
throws IOException {
FieldDescriptor descriptor = fieldWithPath("id").description("one");
new ResponseFieldsSnippet(Arrays.asList(descriptor))
.document(this.operationBuilder.response().content("{\"id\": 67}").build());
.document(operationBuilder.response().content("{\"id\": 67}").build());
assertThat(descriptor.getType()).isNull();
assertThat(this.generatedSnippets.responseFields())
.is(tableWithHeader("Path", "Type", "Description").row("`id`", "`Number`", "one"));
assertThat(snippets.responseFields())
.isTable((table) -> table.withHeader("Path", "Type", "Description").row("`id`", "`Number`", "one"));
}
private String escapeIfNecessary(String input) {
if (this.templateFormat.getId().equals(TemplateFormats.markdown().getId())) {
return input;
}
return input.replace("|", "\\|");
@SnippetTest
void attemptToDocumentFieldsWithNoResponseBody(OperationBuilder operationBuilder) {
assertThatExceptionOfType(SnippetException.class)
.isThrownBy(() -> new ResponseFieldsSnippet(Arrays.asList(fieldWithPath("a").description("one")))
.document(operationBuilder.build()))
.withMessage("Cannot document response fields as the response body is empty");
}
@SnippetTest
void fieldWithExplicitTypeThatDoesNotMatchThePayload(OperationBuilder operationBuilder) {
assertThatExceptionOfType(FieldTypesDoNotMatchException.class)
.isThrownBy(() -> new ResponseFieldsSnippet(
Arrays.asList(fieldWithPath("a").description("one").type(JsonFieldType.OBJECT)))
.document(operationBuilder.response().content("{ \"a\": 5 }}").build()))
.withMessage("The documented type of the field 'a' is Object but the actual type is Number");
}
@SnippetTest
void fieldWithExplicitSpecificTypeThatActuallyVaries(OperationBuilder operationBuilder) {
assertThatExceptionOfType(FieldTypesDoNotMatchException.class)
.isThrownBy(() -> new ResponseFieldsSnippet(
Arrays.asList(fieldWithPath("[].a").description("one").type(JsonFieldType.OBJECT)))
.document(operationBuilder.response().content("[{ \"a\": 5 },{ \"a\": \"b\" }]").build()))
.withMessage("The documented type of the field '[].a' is Object but the actual type is Varies");
}
@SnippetTest
void undocumentedXmlResponseField(OperationBuilder operationBuilder) {
assertThatExceptionOfType(SnippetException.class)
.isThrownBy(() -> new ResponseFieldsSnippet(Collections.<FieldDescriptor>emptyList())
.document(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:");
}
@SnippetTest
void missingXmlAttribute(OperationBuilder operationBuilder) {
assertThatExceptionOfType(SnippetException.class)
.isThrownBy(() -> new ResponseFieldsSnippet(Arrays.asList(fieldWithPath("a").description("one").type("b"),
fieldWithPath("a/@id").description("two").type("c")))
.document(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]");
}
@SnippetTest
void documentedXmlAttributesAreRemoved(OperationBuilder operationBuilder) {
assertThatExceptionOfType(SnippetException.class)
.isThrownBy(
() -> new ResponseFieldsSnippet(Arrays.asList(fieldWithPath("a/@id").description("one").type("a")))
.document(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"));
}
@SnippetTest
void xmlResponseFieldWithNoType(OperationBuilder operationBuilder) {
assertThatExceptionOfType(FieldTypeRequiredException.class)
.isThrownBy(() -> new ResponseFieldsSnippet(Arrays.asList(fieldWithPath("a").description("one")))
.document(operationBuilder.response()
.content("<a>5</a>")
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_XML_VALUE)
.build()));
}
@SnippetTest
void missingXmlResponseField(OperationBuilder operationBuilder) {
assertThatExceptionOfType(SnippetException.class)
.isThrownBy(() -> new ResponseFieldsSnippet(
Arrays.asList(fieldWithPath("a/b").description("one"), fieldWithPath("a").description("one")))
.document(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]");
}
@SnippetTest
void undocumentedXmlResponseFieldAndMissingXmlResponseField(OperationBuilder operationBuilder) {
assertThatExceptionOfType(SnippetException.class)
.isThrownBy(() -> new ResponseFieldsSnippet(Arrays.asList(fieldWithPath("a/b").description("one")))
.document(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]");
}
@SnippetTest
void unsupportedContent(OperationBuilder operationBuilder) {
assertThatExceptionOfType(PayloadHandlingException.class)
.isThrownBy(() -> new ResponseFieldsSnippet(Collections.<FieldDescriptor>emptyList())
.document(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");
}
@SnippetTest
void nonOptionalFieldBeneathArrayThatIsSometimesNull(OperationBuilder operationBuilder) {
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(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]");
}
@SnippetTest
void nonOptionalFieldBeneathArrayThatIsSometimesAbsent(OperationBuilder operationBuilder) {
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(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

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2023 the original author or authors.
* Copyright 2014-2025 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.
@@ -20,7 +20,7 @@ import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import org.junit.Test;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;

View File

@@ -1,68 +0,0 @@
/*
* Copyright 2014-2023 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.request;
import java.util.Arrays;
import java.util.Collections;
import org.junit.Rule;
import org.junit.Test;
import org.springframework.restdocs.snippet.SnippetException;
import org.springframework.restdocs.templates.TemplateFormats;
import org.springframework.restdocs.testfixtures.OperationBuilder;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName;
/**
* Tests for failures when rendering {@link FormParametersSnippet} due to missing or
* undocumented form parameters.
*
* @author Andy Wilkinson
*/
public class FormParametersSnippetFailureTests {
@Rule
public OperationBuilder operationBuilder = new OperationBuilder(TemplateFormats.asciidoctor());
@Test
public void undocumentedParameter() {
assertThatExceptionOfType(SnippetException.class)
.isThrownBy(() -> new FormParametersSnippet(Collections.<ParameterDescriptor>emptyList())
.document(this.operationBuilder.request("http://localhost").content("a=alpha").build()))
.withMessage("Form parameters with the following names were not documented: [a]");
}
@Test
public void missingParameter() {
assertThatExceptionOfType(SnippetException.class)
.isThrownBy(() -> new FormParametersSnippet(Arrays.asList(parameterWithName("a").description("one")))
.document(this.operationBuilder.request("http://localhost").build()))
.withMessage("Form parameters with the following names were not found in the request: [a]");
}
@Test
public void undocumentedAndMissingParameters() {
assertThatExceptionOfType(SnippetException.class)
.isThrownBy(() -> new FormParametersSnippet(Arrays.asList(parameterWithName("a").description("one")))
.document(this.operationBuilder.request("http://localhost").content("b=bravo").build()))
.withMessage("Form parameters with the following names were not documented: [b]. Form parameters"
+ " with the following names were not found in the request: [a]");
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2023 the original author or authors.
* Copyright 2014-2025 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,19 +18,17 @@ package org.springframework.restdocs.request;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collections;
import org.junit.Test;
import org.springframework.restdocs.AbstractSnippetTests;
import org.springframework.restdocs.templates.TemplateEngine;
import org.springframework.restdocs.templates.TemplateFormat;
import org.springframework.restdocs.templates.TemplateFormats;
import org.springframework.restdocs.templates.TemplateResourceResolver;
import org.springframework.restdocs.templates.mustache.MustacheTemplateEngine;
import org.springframework.restdocs.snippet.SnippetException;
import org.springframework.restdocs.testfixtures.jupiter.AssertableSnippets;
import org.springframework.restdocs.testfixtures.jupiter.OperationBuilder;
import org.springframework.restdocs.testfixtures.jupiter.RenderedSnippetTest;
import org.springframework.restdocs.testfixtures.jupiter.SnippetTemplate;
import org.springframework.restdocs.testfixtures.jupiter.SnippetTest;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName;
import static org.springframework.restdocs.snippet.Attributes.attributes;
import static org.springframework.restdocs.snippet.Attributes.key;
@@ -40,147 +38,151 @@ import static org.springframework.restdocs.snippet.Attributes.key;
*
* @author Andy Wilkinson
*/
public class FormParametersSnippetTests extends AbstractSnippetTests {
class FormParametersSnippetTests {
public FormParametersSnippetTests(String name, TemplateFormat templateFormat) {
super(name, templateFormat);
}
@Test
public void formParameters() throws IOException {
@RenderedSnippetTest
void formParameters(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
new FormParametersSnippet(
Arrays.asList(parameterWithName("a").description("one"), parameterWithName("b").description("two")))
.document(this.operationBuilder.request("http://localhost").content("a=alpha&b=bravo").build());
assertThat(this.generatedSnippets.formParameters())
.is(tableWithHeader("Parameter", "Description").row("`a`", "one").row("`b`", "two"));
.document(operationBuilder.request("http://localhost").content("a=alpha&b=bravo").build());
assertThat(snippets.formParameters())
.isTable((table) -> table.withHeader("Parameter", "Description").row("`a`", "one").row("`b`", "two"));
}
@Test
public void formParameterWithNoValue() throws IOException {
@RenderedSnippetTest
void formParameterWithNoValue(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
new FormParametersSnippet(Arrays.asList(parameterWithName("a").description("one")))
.document(this.operationBuilder.request("http://localhost").content("a=").build());
assertThat(this.generatedSnippets.formParameters())
.is(tableWithHeader("Parameter", "Description").row("`a`", "one"));
.document(operationBuilder.request("http://localhost").content("a=").build());
assertThat(snippets.formParameters())
.isTable((table) -> table.withHeader("Parameter", "Description").row("`a`", "one"));
}
@Test
public void ignoredFormParameter() throws IOException {
@RenderedSnippetTest
void ignoredFormParameter(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
new FormParametersSnippet(
Arrays.asList(parameterWithName("a").ignored(), parameterWithName("b").description("two")))
.document(this.operationBuilder.request("http://localhost").content("a=alpha&b=bravo").build());
assertThat(this.generatedSnippets.formParameters())
.is(tableWithHeader("Parameter", "Description").row("`b`", "two"));
.document(operationBuilder.request("http://localhost").content("a=alpha&b=bravo").build());
assertThat(snippets.formParameters())
.isTable((table) -> table.withHeader("Parameter", "Description").row("`b`", "two"));
}
@Test
public void allUndocumentedFormParametersCanBeIgnored() throws IOException {
@RenderedSnippetTest
void allUndocumentedFormParametersCanBeIgnored(OperationBuilder operationBuilder, AssertableSnippets snippets)
throws IOException {
new FormParametersSnippet(Arrays.asList(parameterWithName("b").description("two")), true)
.document(this.operationBuilder.request("http://localhost").content("a=alpha&b=bravo").build());
assertThat(this.generatedSnippets.formParameters())
.is(tableWithHeader("Parameter", "Description").row("`b`", "two"));
.document(operationBuilder.request("http://localhost").content("a=alpha&b=bravo").build());
assertThat(snippets.formParameters())
.isTable((table) -> table.withHeader("Parameter", "Description").row("`b`", "two"));
}
@Test
public void missingOptionalFormParameter() throws IOException {
@RenderedSnippetTest
void missingOptionalFormParameter(OperationBuilder operationBuilder, AssertableSnippets snippets)
throws IOException {
new FormParametersSnippet(Arrays.asList(parameterWithName("a").description("one").optional(),
parameterWithName("b").description("two")))
.document(this.operationBuilder.request("http://localhost").content("b=bravo").build());
assertThat(this.generatedSnippets.formParameters())
.is(tableWithHeader("Parameter", "Description").row("`a`", "one").row("`b`", "two"));
.document(operationBuilder.request("http://localhost").content("b=bravo").build());
assertThat(snippets.formParameters())
.isTable((table) -> table.withHeader("Parameter", "Description").row("`a`", "one").row("`b`", "two"));
}
@Test
public void presentOptionalFormParameter() throws IOException {
@RenderedSnippetTest
void presentOptionalFormParameter(OperationBuilder operationBuilder, AssertableSnippets snippets)
throws IOException {
new FormParametersSnippet(Arrays.asList(parameterWithName("a").description("one").optional()))
.document(this.operationBuilder.request("http://localhost").content("a=alpha").build());
assertThat(this.generatedSnippets.formParameters())
.is(tableWithHeader("Parameter", "Description").row("`a`", "one"));
.document(operationBuilder.request("http://localhost").content("a=alpha").build());
assertThat(snippets.formParameters())
.isTable((table) -> table.withHeader("Parameter", "Description").row("`a`", "one"));
}
@Test
public void formParametersWithCustomAttributes() throws IOException {
TemplateResourceResolver resolver = mock(TemplateResourceResolver.class);
given(resolver.resolveTemplateResource("form-parameters"))
.willReturn(snippetResource("form-parameters-with-title"));
@RenderedSnippetTest
@SnippetTemplate(snippet = "form-parameters", template = "form-parameters-with-title")
void formParametersWithCustomAttributes(OperationBuilder operationBuilder, AssertableSnippets snippets)
throws IOException {
new FormParametersSnippet(
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(this.operationBuilder
.attribute(TemplateEngine.class.getName(), new MustacheTemplateEngine(resolver))
.request("http://localhost")
.content("a=alpha&b=bravo")
.build());
assertThat(this.generatedSnippets.formParameters()).contains("The title");
.document(operationBuilder.request("http://localhost").content("a=alpha&b=bravo").build());
assertThat(snippets.formParameters()).contains("The title");
}
@Test
public void formParametersWithCustomDescriptorAttributes() throws IOException {
TemplateResourceResolver resolver = mock(TemplateResourceResolver.class);
given(resolver.resolveTemplateResource("form-parameters"))
.willReturn(snippetResource("form-parameters-with-extra-column"));
@RenderedSnippetTest
@SnippetTemplate(snippet = "form-parameters", template = "form-parameters-with-extra-column")
void formParametersWithCustomDescriptorAttributes(OperationBuilder operationBuilder, AssertableSnippets snippets)
throws IOException {
new FormParametersSnippet(
Arrays.asList(parameterWithName("a").description("one").attributes(key("foo").value("alpha")),
parameterWithName("b").description("two").attributes(key("foo").value("bravo"))))
.document(this.operationBuilder
.attribute(TemplateEngine.class.getName(), new MustacheTemplateEngine(resolver))
.request("http://localhost")
.content("a=alpha&b=bravo")
.build());
assertThat(this.generatedSnippets.formParameters())
.is(tableWithHeader("Parameter", "Description", "Foo").row("a", "one", "alpha").row("b", "two", "bravo"));
.document(operationBuilder.request("http://localhost").content("a=alpha&b=bravo").build());
assertThat(snippets.formParameters()).isTable((table) -> table.withHeader("Parameter", "Description", "Foo")
.row("a", "one", "alpha")
.row("b", "two", "bravo"));
}
@Test
public void formParametersWithOptionalColumn() throws IOException {
TemplateResourceResolver resolver = mock(TemplateResourceResolver.class);
given(resolver.resolveTemplateResource("form-parameters"))
.willReturn(snippetResource("form-parameters-with-optional-column"));
@RenderedSnippetTest
@SnippetTemplate(snippet = "form-parameters", template = "form-parameters-with-optional-column")
void formParametersWithOptionalColumn(OperationBuilder operationBuilder, AssertableSnippets snippets)
throws IOException {
new FormParametersSnippet(Arrays.asList(parameterWithName("a").description("one").optional(),
parameterWithName("b").description("two")))
.document(this.operationBuilder
.attribute(TemplateEngine.class.getName(), new MustacheTemplateEngine(resolver))
.request("http://localhost")
.content("a=alpha&b=bravo")
.build());
assertThat(this.generatedSnippets.formParameters())
.is(tableWithHeader("Parameter", "Optional", "Description").row("a", "true", "one")
.document(operationBuilder.request("http://localhost").content("a=alpha&b=bravo").build());
assertThat(snippets.formParameters())
.isTable((table) -> table.withHeader("Parameter", "Optional", "Description")
.row("a", "true", "one")
.row("b", "false", "two"));
}
@Test
public void additionalDescriptors() throws IOException {
@RenderedSnippetTest
void additionalDescriptors(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
RequestDocumentation.formParameters(parameterWithName("a").description("one"))
.and(parameterWithName("b").description("two"))
.document(this.operationBuilder.request("http://localhost").content("a=alpha&b=bravo").build());
assertThat(this.generatedSnippets.formParameters())
.is(tableWithHeader("Parameter", "Description").row("`a`", "one").row("`b`", "two"));
.document(operationBuilder.request("http://localhost").content("a=alpha&b=bravo").build());
assertThat(snippets.formParameters())
.isTable((table) -> table.withHeader("Parameter", "Description").row("`a`", "one").row("`b`", "two"));
}
@Test
public void additionalDescriptorsWithRelaxedFormParameters() throws IOException {
@RenderedSnippetTest
void additionalDescriptorsWithRelaxedFormParameters(OperationBuilder operationBuilder, AssertableSnippets snippets)
throws IOException {
RequestDocumentation.relaxedFormParameters(parameterWithName("a").description("one"))
.and(parameterWithName("b").description("two"))
.document(this.operationBuilder.request("http://localhost")
.content("a=alpha&b=bravo&c=undocumented")
.build());
assertThat(this.generatedSnippets.formParameters())
.is(tableWithHeader("Parameter", "Description").row("`a`", "one").row("`b`", "two"));
.document(operationBuilder.request("http://localhost").content("a=alpha&b=bravo&c=undocumented").build());
assertThat(snippets.formParameters())
.isTable((table) -> table.withHeader("Parameter", "Description").row("`a`", "one").row("`b`", "two"));
}
@Test
public void formParametersWithEscapedContent() throws IOException {
@RenderedSnippetTest
void formParametersWithEscapedContent(OperationBuilder operationBuilder, AssertableSnippets snippets)
throws IOException {
RequestDocumentation.formParameters(parameterWithName("Foo|Bar").description("one|two"))
.document(this.operationBuilder.request("http://localhost").content("Foo%7CBar=baz").build());
assertThat(this.generatedSnippets.formParameters()).is(tableWithHeader("Parameter", "Description")
.row(escapeIfNecessary("`Foo|Bar`"), escapeIfNecessary("one|two")));
.document(operationBuilder.request("http://localhost").content("Foo%7CBar=baz").build());
assertThat(snippets.formParameters())
.isTable((table) -> table.withHeader("Parameter", "Description").row("`Foo|Bar`", "one|two"));
}
private String escapeIfNecessary(String input) {
if (this.templateFormat.getId().equals(TemplateFormats.markdown().getId())) {
return input;
}
return input.replace("|", "\\|");
@SnippetTest
void undocumentedParameter(OperationBuilder operationBuilder) {
assertThatExceptionOfType(SnippetException.class)
.isThrownBy(() -> new FormParametersSnippet(Collections.<ParameterDescriptor>emptyList())
.document(operationBuilder.request("http://localhost").content("a=alpha").build()))
.withMessage("Form parameters with the following names were not documented: [a]");
}
@SnippetTest
void missingParameter(OperationBuilder operationBuilder) {
assertThatExceptionOfType(SnippetException.class)
.isThrownBy(() -> new FormParametersSnippet(Arrays.asList(parameterWithName("a").description("one")))
.document(operationBuilder.request("http://localhost").build()))
.withMessage("Form parameters with the following names were not found in the request: [a]");
}
@SnippetTest
void undocumentedAndMissingParameters(OperationBuilder operationBuilder) {
assertThatExceptionOfType(SnippetException.class)
.isThrownBy(() -> new FormParametersSnippet(Arrays.asList(parameterWithName("a").description("one")))
.document(operationBuilder.request("http://localhost").content("b=bravo").build()))
.withMessage("Form parameters with the following names were not documented: [b]. Form parameters"
+ " with the following names were not found in the request: [a]");
}
}

View File

@@ -1,73 +0,0 @@
/*
* Copyright 2014-2023 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.request;
import java.util.Arrays;
import java.util.Collections;
import org.junit.Rule;
import org.junit.Test;
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.assertj.core.api.Assertions.assertThatExceptionOfType;
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 OperationBuilder operationBuilder = new OperationBuilder(TemplateFormats.asciidoctor());
@Test
public void undocumentedPathParameter() {
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() {
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() {
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-2023 the original author or authors.
* Copyright 2014-2025 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,20 +18,20 @@ package org.springframework.restdocs.request;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collections;
import org.junit.Test;
import org.springframework.restdocs.AbstractSnippetTests;
import org.springframework.restdocs.generate.RestDocumentationGenerator;
import org.springframework.restdocs.templates.TemplateEngine;
import org.springframework.restdocs.snippet.SnippetException;
import org.springframework.restdocs.templates.TemplateFormat;
import org.springframework.restdocs.templates.TemplateFormats;
import org.springframework.restdocs.templates.TemplateResourceResolver;
import org.springframework.restdocs.templates.mustache.MustacheTemplateEngine;
import org.springframework.restdocs.testfixtures.jupiter.AssertableSnippets;
import org.springframework.restdocs.testfixtures.jupiter.OperationBuilder;
import org.springframework.restdocs.testfixtures.jupiter.RenderedSnippetTest;
import org.springframework.restdocs.testfixtures.jupiter.SnippetTemplate;
import org.springframework.restdocs.testfixtures.jupiter.SnippetTest;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName;
import static org.springframework.restdocs.snippet.Attributes.attributes;
import static org.springframework.restdocs.snippet.Attributes.key;
@@ -41,164 +41,192 @@ import static org.springframework.restdocs.snippet.Attributes.key;
*
* @author Andy Wilkinson
*/
public class PathParametersSnippetTests extends AbstractSnippetTests {
class PathParametersSnippetTests {
public PathParametersSnippetTests(String name, TemplateFormat templateFormat) {
super(name, templateFormat);
}
@Test
public void pathParameters() throws IOException {
@RenderedSnippetTest
void pathParameters(OperationBuilder operationBuilder, AssertableSnippets snippets, TemplateFormat templateFormat)
throws IOException {
new PathParametersSnippet(
Arrays.asList(parameterWithName("a").description("one"), parameterWithName("b").description("two")))
.document(
this.operationBuilder.attribute(RestDocumentationGenerator.ATTRIBUTE_NAME_URL_TEMPLATE, "/{a}/{b}")
.build());
assertThat(this.generatedSnippets.pathParameters())
.is(tableWithTitleAndHeader(getTitle(), "Parameter", "Description").row("`a`", "one").row("`b`", "two"));
}
@Test
public void ignoredPathParameter() throws IOException {
new PathParametersSnippet(
Arrays.asList(parameterWithName("a").ignored(), parameterWithName("b").description("two")))
.document(
this.operationBuilder.attribute(RestDocumentationGenerator.ATTRIBUTE_NAME_URL_TEMPLATE, "/{a}/{b}")
.build());
assertThat(this.generatedSnippets.pathParameters())
.is(tableWithTitleAndHeader(getTitle(), "Parameter", "Description").row("`b`", "two"));
}
@Test
public void allUndocumentedPathParametersCanBeIgnored() throws IOException {
new PathParametersSnippet(Arrays.asList(parameterWithName("b").description("two")), true).document(
this.operationBuilder.attribute(RestDocumentationGenerator.ATTRIBUTE_NAME_URL_TEMPLATE, "/{a}/{b}")
.build());
assertThat(this.generatedSnippets.pathParameters())
.is(tableWithTitleAndHeader(getTitle(), "Parameter", "Description").row("`b`", "two"));
}
@Test
public void missingOptionalPathParameter() throws IOException {
new PathParametersSnippet(Arrays.asList(parameterWithName("a").description("one"),
parameterWithName("b").description("two").optional()))
.document(this.operationBuilder.attribute(RestDocumentationGenerator.ATTRIBUTE_NAME_URL_TEMPLATE, "/{a}")
.document(operationBuilder.attribute(RestDocumentationGenerator.ATTRIBUTE_NAME_URL_TEMPLATE, "/{a}/{b}")
.build());
assertThat(this.generatedSnippets.pathParameters())
.is(tableWithTitleAndHeader(getTitle("/{a}"), "Parameter", "Description").row("`a`", "one")
assertThat(snippets.pathParameters())
.isTable((table) -> table.withTitleAndHeader(getTitle(templateFormat), "Parameter", "Description")
.row("`a`", "one")
.row("`b`", "two"));
}
@Test
public void presentOptionalPathParameter() throws IOException {
new PathParametersSnippet(Arrays.asList(parameterWithName("a").description("one").optional()))
.document(this.operationBuilder.attribute(RestDocumentationGenerator.ATTRIBUTE_NAME_URL_TEMPLATE, "/{a}")
@RenderedSnippetTest
void ignoredPathParameter(OperationBuilder operationBuilder, AssertableSnippets snippets,
TemplateFormat templateFormat) throws IOException {
new PathParametersSnippet(
Arrays.asList(parameterWithName("a").ignored(), parameterWithName("b").description("two")))
.document(operationBuilder.attribute(RestDocumentationGenerator.ATTRIBUTE_NAME_URL_TEMPLATE, "/{a}/{b}")
.build());
assertThat(this.generatedSnippets.pathParameters())
.is(tableWithTitleAndHeader(getTitle("/{a}"), "Parameter", "Description").row("`a`", "one"));
assertThat(snippets.pathParameters())
.isTable((table) -> table.withTitleAndHeader(getTitle(templateFormat), "Parameter", "Description")
.row("`b`", "two"));
}
@Test
public void pathParametersWithQueryString() throws IOException {
@RenderedSnippetTest
void allUndocumentedPathParametersCanBeIgnored(OperationBuilder operationBuilder, AssertableSnippets snippets,
TemplateFormat templateFormat) throws IOException {
new PathParametersSnippet(Arrays.asList(parameterWithName("b").description("two")), true).document(
operationBuilder.attribute(RestDocumentationGenerator.ATTRIBUTE_NAME_URL_TEMPLATE, "/{a}/{b}").build());
assertThat(snippets.pathParameters())
.isTable((table) -> table.withTitleAndHeader(getTitle(templateFormat), "Parameter", "Description")
.row("`b`", "two"));
}
@RenderedSnippetTest
void missingOptionalPathParameter(OperationBuilder operationBuilder, AssertableSnippets snippets,
TemplateFormat templateFormat) throws IOException {
new PathParametersSnippet(Arrays.asList(parameterWithName("a").description("one"),
parameterWithName("b").description("two").optional()))
.document(
operationBuilder.attribute(RestDocumentationGenerator.ATTRIBUTE_NAME_URL_TEMPLATE, "/{a}").build());
assertThat(snippets.pathParameters())
.isTable((table) -> table.withTitleAndHeader(getTitle(templateFormat, "/{a}"), "Parameter", "Description")
.row("`a`", "one")
.row("`b`", "two"));
}
@RenderedSnippetTest
void presentOptionalPathParameter(OperationBuilder operationBuilder, AssertableSnippets snippets,
TemplateFormat templateFormat) throws IOException {
new PathParametersSnippet(Arrays.asList(parameterWithName("a").description("one").optional())).document(
operationBuilder.attribute(RestDocumentationGenerator.ATTRIBUTE_NAME_URL_TEMPLATE, "/{a}").build());
assertThat(snippets.pathParameters())
.isTable((table) -> table.withTitleAndHeader(getTitle(templateFormat, "/{a}"), "Parameter", "Description")
.row("`a`", "one"));
}
@RenderedSnippetTest
void pathParametersWithQueryString(OperationBuilder operationBuilder, AssertableSnippets snippets,
TemplateFormat templateFormat) throws IOException {
new PathParametersSnippet(
Arrays.asList(parameterWithName("a").description("one"), parameterWithName("b").description("two")))
.document(this.operationBuilder
.document(operationBuilder
.attribute(RestDocumentationGenerator.ATTRIBUTE_NAME_URL_TEMPLATE, "/{a}/{b}?foo=bar")
.build());
assertThat(this.generatedSnippets.pathParameters())
.is(tableWithTitleAndHeader(getTitle(), "Parameter", "Description").row("`a`", "one").row("`b`", "two"));
assertThat(snippets.pathParameters())
.isTable((table) -> table.withTitleAndHeader(getTitle(templateFormat), "Parameter", "Description")
.row("`a`", "one")
.row("`b`", "two"));
}
@Test
public void pathParametersWithQueryStringWithParameters() throws IOException {
@RenderedSnippetTest
void pathParametersWithQueryStringWithParameters(OperationBuilder operationBuilder, AssertableSnippets snippets,
TemplateFormat templateFormat) throws IOException {
new PathParametersSnippet(
Arrays.asList(parameterWithName("a").description("one"), parameterWithName("b").description("two")))
.document(this.operationBuilder
.document(operationBuilder
.attribute(RestDocumentationGenerator.ATTRIBUTE_NAME_URL_TEMPLATE, "/{a}/{b}?foo={c}")
.build());
assertThat(this.generatedSnippets.pathParameters())
.is(tableWithTitleAndHeader(getTitle(), "Parameter", "Description").row("`a`", "one").row("`b`", "two"));
assertThat(snippets.pathParameters())
.isTable((table) -> table.withTitleAndHeader(getTitle(templateFormat), "Parameter", "Description")
.row("`a`", "one")
.row("`b`", "two"));
}
@Test
public void pathParametersWithCustomAttributes() throws IOException {
TemplateResourceResolver resolver = mock(TemplateResourceResolver.class);
given(resolver.resolveTemplateResource("path-parameters"))
.willReturn(snippetResource("path-parameters-with-title"));
@RenderedSnippetTest
@SnippetTemplate(snippet = "path-parameters", template = "path-parameters-with-title")
void pathParametersWithCustomAttributes(OperationBuilder operationBuilder, AssertableSnippets snippets,
TemplateFormat templateFormat) throws IOException {
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(
this.operationBuilder.attribute(RestDocumentationGenerator.ATTRIBUTE_NAME_URL_TEMPLATE, "/{a}/{b}")
.attribute(TemplateEngine.class.getName(), new MustacheTemplateEngine(resolver))
.build());
assertThat(this.generatedSnippets.pathParameters()).contains("The title");
.document(operationBuilder.attribute(RestDocumentationGenerator.ATTRIBUTE_NAME_URL_TEMPLATE, "/{a}/{b}")
.build());
assertThat(snippets.pathParameters()).contains("The title");
}
@Test
public void pathParametersWithCustomDescriptorAttributes() throws IOException {
TemplateResourceResolver resolver = mock(TemplateResourceResolver.class);
given(resolver.resolveTemplateResource("path-parameters"))
.willReturn(snippetResource("path-parameters-with-extra-column"));
@RenderedSnippetTest
@SnippetTemplate(snippet = "path-parameters", template = "path-parameters-with-extra-column")
void pathParametersWithCustomDescriptorAttributes(OperationBuilder operationBuilder, AssertableSnippets snippets,
TemplateFormat templateFormat) throws IOException {
new PathParametersSnippet(
Arrays.asList(parameterWithName("a").description("one").attributes(key("foo").value("alpha")),
parameterWithName("b").description("two").attributes(key("foo").value("bravo"))))
.document(
this.operationBuilder.attribute(RestDocumentationGenerator.ATTRIBUTE_NAME_URL_TEMPLATE, "/{a}/{b}")
.attribute(TemplateEngine.class.getName(), new MustacheTemplateEngine(resolver))
.build());
assertThat(this.generatedSnippets.pathParameters())
.is(tableWithHeader("Parameter", "Description", "Foo").row("a", "one", "alpha").row("b", "two", "bravo"));
.document(operationBuilder.attribute(RestDocumentationGenerator.ATTRIBUTE_NAME_URL_TEMPLATE, "/{a}/{b}")
.build());
assertThat(snippets.pathParameters()).isTable((table) -> table.withHeader("Parameter", "Description", "Foo")
.row("a", "one", "alpha")
.row("b", "two", "bravo"));
}
@Test
public void additionalDescriptors() throws IOException {
@RenderedSnippetTest
void additionalDescriptors(OperationBuilder operationBuilder, AssertableSnippets snippets,
TemplateFormat templateFormat) throws IOException {
RequestDocumentation.pathParameters(parameterWithName("a").description("one"))
.and(parameterWithName("b").description("two"))
.document(
this.operationBuilder.attribute(RestDocumentationGenerator.ATTRIBUTE_NAME_URL_TEMPLATE, "/{a}/{b}")
.build());
assertThat(this.generatedSnippets.pathParameters())
.is(tableWithTitleAndHeader(getTitle(), "Parameter", "Description").row("`a`", "one").row("`b`", "two"));
.document(operationBuilder.attribute(RestDocumentationGenerator.ATTRIBUTE_NAME_URL_TEMPLATE, "/{a}/{b}")
.build());
assertThat(snippets.pathParameters()).isTable(
(table) -> table.withTitleAndHeader(getTitle(templateFormat, "/{a}/{b}"), "Parameter", "Description")
.row("`a`", "one")
.row("`b`", "two"));
}
@Test
public void additionalDescriptorsWithRelaxedRequestParameters() throws IOException {
@RenderedSnippetTest
void additionalDescriptorsWithRelaxedRequestParameters(OperationBuilder operationBuilder,
AssertableSnippets snippets, TemplateFormat templateFormat) throws IOException {
RequestDocumentation.relaxedPathParameters(parameterWithName("a").description("one"))
.and(parameterWithName("b").description("two"))
.document(this.operationBuilder
.attribute(RestDocumentationGenerator.ATTRIBUTE_NAME_URL_TEMPLATE, "/{a}/{b}/{c}")
.document(operationBuilder.attribute(RestDocumentationGenerator.ATTRIBUTE_NAME_URL_TEMPLATE, "/{a}/{b}/{c}")
.build());
assertThat(this.generatedSnippets.pathParameters())
.is(tableWithTitleAndHeader(getTitle("/{a}/{b}/{c}"), "Parameter", "Description").row("`a`", "one")
.row("`b`", "two"));
assertThat(snippets.pathParameters()).isTable((table) -> table
.withTitleAndHeader(getTitle(templateFormat, "/{a}/{b}/{c}"), "Parameter", "Description")
.row("`a`", "one")
.row("`b`", "two"));
}
@Test
public void pathParametersWithEscapedContent() throws IOException {
@RenderedSnippetTest
void pathParametersWithEscapedContent(OperationBuilder operationBuilder, AssertableSnippets snippets,
TemplateFormat templateFormat) throws IOException {
RequestDocumentation.pathParameters(parameterWithName("Foo|Bar").description("one|two"))
.document(
this.operationBuilder.attribute(RestDocumentationGenerator.ATTRIBUTE_NAME_URL_TEMPLATE, "{Foo|Bar}")
.build());
assertThat(this.generatedSnippets.pathParameters())
.is(tableWithTitleAndHeader(getTitle("{Foo|Bar}"), "Parameter", "Description")
.row(escapeIfNecessary("`Foo|Bar`"), escapeIfNecessary("one|two")));
.document(operationBuilder.attribute(RestDocumentationGenerator.ATTRIBUTE_NAME_URL_TEMPLATE, "{Foo|Bar}")
.build());
assertThat(snippets.pathParameters()).isTable(
(table) -> table.withTitleAndHeader(getTitle(templateFormat, "{Foo|Bar}"), "Parameter", "Description")
.row("`Foo|Bar`", "one|two"));
}
private String escapeIfNecessary(String input) {
if (this.templateFormat.getId().equals(TemplateFormats.markdown().getId())) {
return input;
}
return input.replace("|", "\\|");
@SnippetTest
void undocumentedPathParameter(OperationBuilder operationBuilder) {
assertThatExceptionOfType(SnippetException.class)
.isThrownBy(() -> new PathParametersSnippet(Collections.<ParameterDescriptor>emptyList())
.document(operationBuilder.attribute(RestDocumentationGenerator.ATTRIBUTE_NAME_URL_TEMPLATE, "/{a}/")
.build()))
.withMessage("Path parameters with the following names were not documented: [a]");
}
private String getTitle() {
return getTitle("/{a}/{b}");
@SnippetTest
void missingPathParameter(OperationBuilder operationBuilder) {
assertThatExceptionOfType(SnippetException.class)
.isThrownBy(() -> new PathParametersSnippet(Arrays.asList(parameterWithName("a").description("one")))
.document(operationBuilder.attribute(RestDocumentationGenerator.ATTRIBUTE_NAME_URL_TEMPLATE, "/")
.build()))
.withMessage("Path parameters with the following names were not found in the request: [a]");
}
private String getTitle(String title) {
if (this.templateFormat.getId().equals(TemplateFormats.asciidoctor().getId())) {
@SnippetTest
void undocumentedAndMissingPathParameters(OperationBuilder operationBuilder) {
assertThatExceptionOfType(SnippetException.class)
.isThrownBy(() -> new PathParametersSnippet(Arrays.asList(parameterWithName("a").description("one")))
.document(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]");
}
private String getTitle(TemplateFormat templateFormat) {
return getTitle(templateFormat, "/{a}/{b}");
}
private String getTitle(TemplateFormat templateFormat, String title) {
if (templateFormat.getId().equals(TemplateFormats.asciidoctor().getId())) {
return "+" + title + "+";
}
return "`" + title + "`";

View File

@@ -1,68 +0,0 @@
/*
* Copyright 2014-2023 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.request;
import java.util.Arrays;
import java.util.Collections;
import org.junit.Rule;
import org.junit.Test;
import org.springframework.restdocs.snippet.SnippetException;
import org.springframework.restdocs.templates.TemplateFormats;
import org.springframework.restdocs.testfixtures.OperationBuilder;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName;
/**
* Tests for failures when rendering {@link QueryParametersSnippet} due to missing or
* undocumented query parameters.
*
* @author Andy Wilkinson
*/
public class QueryParametersSnippetFailureTests {
@Rule
public OperationBuilder operationBuilder = new OperationBuilder(TemplateFormats.asciidoctor());
@Test
public void undocumentedParameter() {
assertThatExceptionOfType(SnippetException.class)
.isThrownBy(() -> new QueryParametersSnippet(Collections.<ParameterDescriptor>emptyList())
.document(this.operationBuilder.request("http://localhost?a=alpha").build()))
.withMessage("Query parameters with the following names were not documented: [a]");
}
@Test
public void missingParameter() {
assertThatExceptionOfType(SnippetException.class)
.isThrownBy(() -> new QueryParametersSnippet(Arrays.asList(parameterWithName("a").description("one")))
.document(this.operationBuilder.request("http://localhost").build()))
.withMessage("Query parameters with the following names were not found in the request: [a]");
}
@Test
public void undocumentedAndMissingParameters() {
assertThatExceptionOfType(SnippetException.class)
.isThrownBy(() -> new QueryParametersSnippet(Arrays.asList(parameterWithName("a").description("one")))
.document(this.operationBuilder.request("http://localhost?b=bravo").build()))
.withMessage("Query parameters with the following names were not documented: [b]. Query parameters"
+ " with the following names were not found in the request: [a]");
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2023 the original author or authors.
* Copyright 2014-2025 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,19 +18,17 @@ package org.springframework.restdocs.request;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collections;
import org.junit.Test;
import org.springframework.restdocs.AbstractSnippetTests;
import org.springframework.restdocs.templates.TemplateEngine;
import org.springframework.restdocs.templates.TemplateFormat;
import org.springframework.restdocs.templates.TemplateFormats;
import org.springframework.restdocs.templates.TemplateResourceResolver;
import org.springframework.restdocs.templates.mustache.MustacheTemplateEngine;
import org.springframework.restdocs.snippet.SnippetException;
import org.springframework.restdocs.testfixtures.jupiter.AssertableSnippets;
import org.springframework.restdocs.testfixtures.jupiter.OperationBuilder;
import org.springframework.restdocs.testfixtures.jupiter.RenderedSnippetTest;
import org.springframework.restdocs.testfixtures.jupiter.SnippetTemplate;
import org.springframework.restdocs.testfixtures.jupiter.SnippetTest;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName;
import static org.springframework.restdocs.snippet.Attributes.attributes;
import static org.springframework.restdocs.snippet.Attributes.key;
@@ -40,142 +38,151 @@ import static org.springframework.restdocs.snippet.Attributes.key;
*
* @author Andy Wilkinson
*/
public class QueryParametersSnippetTests extends AbstractSnippetTests {
class QueryParametersSnippetTests {
public QueryParametersSnippetTests(String name, TemplateFormat templateFormat) {
super(name, templateFormat);
}
@Test
public void queryParameters() throws IOException {
@RenderedSnippetTest
void queryParameters(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
new QueryParametersSnippet(
Arrays.asList(parameterWithName("a").description("one"), parameterWithName("b").description("two")))
.document(this.operationBuilder.request("http://localhost?a=alpha&b=bravo").build());
assertThat(this.generatedSnippets.queryParameters())
.is(tableWithHeader("Parameter", "Description").row("`a`", "one").row("`b`", "two"));
.document(operationBuilder.request("http://localhost?a=alpha&b=bravo").build());
assertThat(snippets.queryParameters())
.isTable((table) -> table.withHeader("Parameter", "Description").row("`a`", "one").row("`b`", "two"));
}
@Test
public void queryParameterWithNoValue() throws IOException {
@RenderedSnippetTest
void queryParameterWithNoValue(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
new QueryParametersSnippet(Arrays.asList(parameterWithName("a").description("one")))
.document(this.operationBuilder.request("http://localhost?a").build());
assertThat(this.generatedSnippets.queryParameters())
.is(tableWithHeader("Parameter", "Description").row("`a`", "one"));
.document(operationBuilder.request("http://localhost?a").build());
assertThat(snippets.queryParameters())
.isTable((table) -> table.withHeader("Parameter", "Description").row("`a`", "one"));
}
@Test
public void ignoredQueryParameter() throws IOException {
@RenderedSnippetTest
void ignoredQueryParameter(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
new QueryParametersSnippet(
Arrays.asList(parameterWithName("a").ignored(), parameterWithName("b").description("two")))
.document(this.operationBuilder.request("http://localhost?a=alpha&b=bravo").build());
assertThat(this.generatedSnippets.queryParameters())
.is(tableWithHeader("Parameter", "Description").row("`b`", "two"));
.document(operationBuilder.request("http://localhost?a=alpha&b=bravo").build());
assertThat(snippets.queryParameters())
.isTable((table) -> table.withHeader("Parameter", "Description").row("`b`", "two"));
}
@Test
public void allUndocumentedQueryParametersCanBeIgnored() throws IOException {
@RenderedSnippetTest
void allUndocumentedQueryParametersCanBeIgnored(OperationBuilder operationBuilder, AssertableSnippets snippets)
throws IOException {
new QueryParametersSnippet(Arrays.asList(parameterWithName("b").description("two")), true)
.document(this.operationBuilder.request("http://localhost?a=alpha&b=bravo").build());
assertThat(this.generatedSnippets.queryParameters())
.is(tableWithHeader("Parameter", "Description").row("`b`", "two"));
.document(operationBuilder.request("http://localhost?a=alpha&b=bravo").build());
assertThat(snippets.queryParameters())
.isTable((table) -> table.withHeader("Parameter", "Description").row("`b`", "two"));
}
@Test
public void missingOptionalQueryParameter() throws IOException {
@RenderedSnippetTest
void missingOptionalQueryParameter(OperationBuilder operationBuilder, AssertableSnippets snippets)
throws IOException {
new QueryParametersSnippet(Arrays.asList(parameterWithName("a").description("one").optional(),
parameterWithName("b").description("two")))
.document(this.operationBuilder.request("http://localhost?b=bravo").build());
assertThat(this.generatedSnippets.queryParameters())
.is(tableWithHeader("Parameter", "Description").row("`a`", "one").row("`b`", "two"));
.document(operationBuilder.request("http://localhost?b=bravo").build());
assertThat(snippets.queryParameters())
.isTable((table) -> table.withHeader("Parameter", "Description").row("`a`", "one").row("`b`", "two"));
}
@Test
public void presentOptionalQueryParameter() throws IOException {
@RenderedSnippetTest
void presentOptionalQueryParameter(OperationBuilder operationBuilder, AssertableSnippets snippets)
throws IOException {
new QueryParametersSnippet(Arrays.asList(parameterWithName("a").description("one").optional()))
.document(this.operationBuilder.request("http://localhost?a=alpha").build());
assertThat(this.generatedSnippets.queryParameters())
.is(tableWithHeader("Parameter", "Description").row("`a`", "one"));
.document(operationBuilder.request("http://localhost?a=alpha").build());
assertThat(snippets.queryParameters())
.isTable((table) -> table.withHeader("Parameter", "Description").row("`a`", "one"));
}
@Test
public void queryParametersWithCustomAttributes() throws IOException {
TemplateResourceResolver resolver = mock(TemplateResourceResolver.class);
given(resolver.resolveTemplateResource("query-parameters"))
.willReturn(snippetResource("query-parameters-with-title"));
@RenderedSnippetTest
@SnippetTemplate(snippet = "query-parameters", template = "query-parameters-with-title")
void queryParametersWithCustomAttributes(OperationBuilder operationBuilder, AssertableSnippets snippets)
throws IOException {
new QueryParametersSnippet(
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(this.operationBuilder
.attribute(TemplateEngine.class.getName(), new MustacheTemplateEngine(resolver))
.request("http://localhost?a=alpha&b=bravo")
.build());
assertThat(this.generatedSnippets.queryParameters()).contains("The title");
.document(operationBuilder.request("http://localhost?a=alpha&b=bravo").build());
assertThat(snippets.queryParameters()).contains("The title");
}
@Test
public void queryParametersWithCustomDescriptorAttributes() throws IOException {
TemplateResourceResolver resolver = mock(TemplateResourceResolver.class);
given(resolver.resolveTemplateResource("query-parameters"))
.willReturn(snippetResource("query-parameters-with-extra-column"));
@RenderedSnippetTest
@SnippetTemplate(snippet = "query-parameters", template = "query-parameters-with-extra-column")
void queryParametersWithCustomDescriptorAttributes(OperationBuilder operationBuilder, AssertableSnippets snippets)
throws IOException {
new QueryParametersSnippet(
Arrays.asList(parameterWithName("a").description("one").attributes(key("foo").value("alpha")),
parameterWithName("b").description("two").attributes(key("foo").value("bravo"))))
.document(this.operationBuilder
.attribute(TemplateEngine.class.getName(), new MustacheTemplateEngine(resolver))
.request("http://localhost?a=alpha&b=bravo")
.build());
assertThat(this.generatedSnippets.queryParameters())
.is(tableWithHeader("Parameter", "Description", "Foo").row("a", "one", "alpha").row("b", "two", "bravo"));
.document(operationBuilder.request("http://localhost?a=alpha&b=bravo").build());
assertThat(snippets.queryParameters()).isTable((table) -> table.withHeader("Parameter", "Description", "Foo")
.row("a", "one", "alpha")
.row("b", "two", "bravo"));
}
@Test
public void queryParametersWithOptionalColumn() throws IOException {
TemplateResourceResolver resolver = mock(TemplateResourceResolver.class);
given(resolver.resolveTemplateResource("query-parameters"))
.willReturn(snippetResource("query-parameters-with-optional-column"));
@RenderedSnippetTest
@SnippetTemplate(snippet = "query-parameters", template = "query-parameters-with-optional-column")
void queryParametersWithOptionalColumn(OperationBuilder operationBuilder, AssertableSnippets snippets)
throws IOException {
new QueryParametersSnippet(Arrays.asList(parameterWithName("a").description("one").optional(),
parameterWithName("b").description("two")))
.document(this.operationBuilder
.attribute(TemplateEngine.class.getName(), new MustacheTemplateEngine(resolver))
.request("http://localhost?a=alpha&b=bravo")
.build());
assertThat(this.generatedSnippets.queryParameters())
.is(tableWithHeader("Parameter", "Optional", "Description").row("a", "true", "one")
.document(operationBuilder.request("http://localhost?a=alpha&b=bravo").build());
assertThat(snippets.queryParameters())
.isTable((table) -> table.withHeader("Parameter", "Optional", "Description")
.row("a", "true", "one")
.row("b", "false", "two"));
}
@Test
public void additionalDescriptors() throws IOException {
@RenderedSnippetTest
void additionalDescriptors(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
RequestDocumentation.queryParameters(parameterWithName("a").description("one"))
.and(parameterWithName("b").description("two"))
.document(this.operationBuilder.request("http://localhost?a=alpha&b=bravo").build());
assertThat(this.generatedSnippets.queryParameters())
.is(tableWithHeader("Parameter", "Description").row("`a`", "one").row("`b`", "two"));
.document(operationBuilder.request("http://localhost?a=alpha&b=bravo").build());
assertThat(snippets.queryParameters())
.isTable((table) -> table.withHeader("Parameter", "Description").row("`a`", "one").row("`b`", "two"));
}
@Test
public void additionalDescriptorsWithRelaxedQueryParameters() throws IOException {
@RenderedSnippetTest
void additionalDescriptorsWithRelaxedQueryParameters(OperationBuilder operationBuilder, AssertableSnippets snippets)
throws IOException {
RequestDocumentation.relaxedQueryParameters(parameterWithName("a").description("one"))
.and(parameterWithName("b").description("two"))
.document(this.operationBuilder.request("http://localhost?a=alpha&b=bravo&c=undocumented").build());
assertThat(this.generatedSnippets.queryParameters())
.is(tableWithHeader("Parameter", "Description").row("`a`", "one").row("`b`", "two"));
.document(operationBuilder.request("http://localhost?a=alpha&b=bravo&c=undocumented").build());
assertThat(snippets.queryParameters())
.isTable((table) -> table.withHeader("Parameter", "Description").row("`a`", "one").row("`b`", "two"));
}
@Test
public void queryParametersWithEscapedContent() throws IOException {
@RenderedSnippetTest
void queryParametersWithEscapedContent(OperationBuilder operationBuilder, AssertableSnippets snippets)
throws IOException {
RequestDocumentation.queryParameters(parameterWithName("Foo|Bar").description("one|two"))
.document(this.operationBuilder.request("http://localhost?Foo%7CBar=baz").build());
assertThat(this.generatedSnippets.queryParameters()).is(tableWithHeader("Parameter", "Description")
.row(escapeIfNecessary("`Foo|Bar`"), escapeIfNecessary("one|two")));
.document(operationBuilder.request("http://localhost?Foo%7CBar=baz").build());
assertThat(snippets.queryParameters())
.isTable((table) -> table.withHeader("Parameter", "Description").row("`Foo|Bar`", "one|two"));
}
private String escapeIfNecessary(String input) {
if (this.templateFormat.getId().equals(TemplateFormats.markdown().getId())) {
return input;
}
return input.replace("|", "\\|");
@SnippetTest
void undocumentedParameter(OperationBuilder operationBuilder) {
assertThatExceptionOfType(SnippetException.class)
.isThrownBy(() -> new QueryParametersSnippet(Collections.<ParameterDescriptor>emptyList())
.document(operationBuilder.request("http://localhost?a=alpha").build()))
.withMessage("Query parameters with the following names were not documented: [a]");
}
@SnippetTest
void missingParameter(OperationBuilder operationBuilder) {
assertThatExceptionOfType(SnippetException.class)
.isThrownBy(() -> new QueryParametersSnippet(Arrays.asList(parameterWithName("a").description("one")))
.document(operationBuilder.request("http://localhost").build()))
.withMessage("Query parameters with the following names were not found in the request: [a]");
}
@SnippetTest
void undocumentedAndMissingParameters(OperationBuilder operationBuilder) {
assertThatExceptionOfType(SnippetException.class)
.isThrownBy(() -> new QueryParametersSnippet(Arrays.asList(parameterWithName("a").description("one")))
.document(operationBuilder.request("http://localhost?b=bravo").build()))
.withMessage("Query parameters with the following names were not documented: [b]. Query parameters"
+ " with the following names were not found in the request: [a]");
}
}

View File

@@ -1,68 +0,0 @@
/*
* Copyright 2014-2023 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.request;
import java.util.Arrays;
import java.util.Collections;
import org.junit.Rule;
import org.junit.Test;
import org.springframework.restdocs.snippet.SnippetException;
import org.springframework.restdocs.templates.TemplateFormats;
import org.springframework.restdocs.testfixtures.OperationBuilder;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.springframework.restdocs.request.RequestDocumentation.partWithName;
/**
* Tests for failures when rendering {@link RequestPartsSnippet} due to missing or
* undocumented request parts.
*
* @author Andy Wilkinson
*/
public class RequestPartsSnippetFailureTests {
@Rule
public OperationBuilder operationBuilder = new OperationBuilder(TemplateFormats.asciidoctor());
@Test
public void undocumentedPart() {
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() {
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() {
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-2023 the original author or authors.
* Copyright 2014-2025 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,19 +18,17 @@ package org.springframework.restdocs.request;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collections;
import org.junit.Test;
import org.springframework.restdocs.AbstractSnippetTests;
import org.springframework.restdocs.templates.TemplateEngine;
import org.springframework.restdocs.templates.TemplateFormat;
import org.springframework.restdocs.templates.TemplateFormats;
import org.springframework.restdocs.templates.TemplateResourceResolver;
import org.springframework.restdocs.templates.mustache.MustacheTemplateEngine;
import org.springframework.restdocs.snippet.SnippetException;
import org.springframework.restdocs.testfixtures.jupiter.AssertableSnippets;
import org.springframework.restdocs.testfixtures.jupiter.OperationBuilder;
import org.springframework.restdocs.testfixtures.jupiter.RenderedSnippetTest;
import org.springframework.restdocs.testfixtures.jupiter.SnippetTemplate;
import org.springframework.restdocs.testfixtures.jupiter.SnippetTest;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.springframework.restdocs.request.RequestDocumentation.partWithName;
import static org.springframework.restdocs.snippet.Attributes.attributes;
import static org.springframework.restdocs.snippet.Attributes.key;
@@ -40,145 +38,157 @@ import static org.springframework.restdocs.snippet.Attributes.key;
*
* @author Andy Wilkinson
*/
public class RequestPartsSnippetTests extends AbstractSnippetTests {
class RequestPartsSnippetTests {
public RequestPartsSnippetTests(String name, TemplateFormat templateFormat) {
super(name, templateFormat);
}
@Test
public void requestParts() throws IOException {
@RenderedSnippetTest
void requestParts(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
new RequestPartsSnippet(
Arrays.asList(partWithName("a").description("one"), partWithName("b").description("two")))
.document(this.operationBuilder.request("http://localhost")
.document(operationBuilder.request("http://localhost")
.part("a", "bravo".getBytes())
.and()
.part("b", "bravo".getBytes())
.build());
assertThat(this.generatedSnippets.requestParts())
.is(tableWithHeader("Part", "Description").row("`a`", "one").row("`b`", "two"));
assertThat(snippets.requestParts())
.isTable((table) -> table.withHeader("Part", "Description").row("`a`", "one").row("`b`", "two"));
}
@Test
public void ignoredRequestPart() throws IOException {
@RenderedSnippetTest
void ignoredRequestPart(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
new RequestPartsSnippet(Arrays.asList(partWithName("a").ignored(), partWithName("b").description("two")))
.document(this.operationBuilder.request("http://localhost")
.document(operationBuilder.request("http://localhost")
.part("a", "bravo".getBytes())
.and()
.part("b", "bravo".getBytes())
.build());
assertThat(this.generatedSnippets.requestParts()).is(tableWithHeader("Part", "Description").row("`b`", "two"));
assertThat(snippets.requestParts())
.isTable((table) -> table.withHeader("Part", "Description").row("`b`", "two"));
}
@Test
public void allUndocumentedRequestPartsCanBeIgnored() throws IOException {
@RenderedSnippetTest
void allUndocumentedRequestPartsCanBeIgnored(OperationBuilder operationBuilder, AssertableSnippets snippets)
throws IOException {
new RequestPartsSnippet(Arrays.asList(partWithName("b").description("two")), true)
.document(this.operationBuilder.request("http://localhost")
.document(operationBuilder.request("http://localhost")
.part("a", "bravo".getBytes())
.and()
.part("b", "bravo".getBytes())
.build());
assertThat(this.generatedSnippets.requestParts()).is(tableWithHeader("Part", "Description").row("`b`", "two"));
assertThat(snippets.requestParts())
.isTable((table) -> table.withHeader("Part", "Description").row("`b`", "two"));
}
@Test
public void missingOptionalRequestPart() throws IOException {
@RenderedSnippetTest
void missingOptionalRequestPart(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
new RequestPartsSnippet(
Arrays.asList(partWithName("a").description("one").optional(), partWithName("b").description("two")))
.document(this.operationBuilder.request("http://localhost").part("b", "bravo".getBytes()).build());
assertThat(this.generatedSnippets.requestParts())
.is(tableWithHeader("Part", "Description").row("`a`", "one").row("`b`", "two"));
.document(operationBuilder.request("http://localhost").part("b", "bravo".getBytes()).build());
assertThat(snippets.requestParts())
.isTable((table) -> table.withHeader("Part", "Description").row("`a`", "one").row("`b`", "two"));
}
@Test
public void presentOptionalRequestPart() throws IOException {
@RenderedSnippetTest
void presentOptionalRequestPart(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
new RequestPartsSnippet(Arrays.asList(partWithName("a").description("one").optional()))
.document(this.operationBuilder.request("http://localhost").part("a", "one".getBytes()).build());
assertThat(this.generatedSnippets.requestParts()).is(tableWithHeader("Part", "Description").row("`a`", "one"));
.document(operationBuilder.request("http://localhost").part("a", "one".getBytes()).build());
assertThat(snippets.requestParts())
.isTable((table) -> table.withHeader("Part", "Description").row("`a`", "one"));
}
@Test
public void requestPartsWithCustomAttributes() throws IOException {
TemplateResourceResolver resolver = mock(TemplateResourceResolver.class);
given(resolver.resolveTemplateResource("request-parts"))
.willReturn(snippetResource("request-parts-with-title"));
@RenderedSnippetTest
@SnippetTemplate(snippet = "request-parts", template = "request-parts-with-title")
void requestPartsWithCustomAttributes(OperationBuilder operationBuilder, AssertableSnippets snippets)
throws IOException {
new RequestPartsSnippet(
Arrays.asList(partWithName("a").description("one").attributes(key("foo").value("alpha")),
partWithName("b").description("two").attributes(key("foo").value("bravo"))),
attributes(key("title").value("The title")))
.document(this.operationBuilder
.attribute(TemplateEngine.class.getName(), new MustacheTemplateEngine(resolver))
.request("http://localhost")
.document(operationBuilder.request("http://localhost")
.part("a", "alpha".getBytes())
.and()
.part("b", "bravo".getBytes())
.build());
assertThat(this.generatedSnippets.requestParts()).contains("The title");
assertThat(snippets.requestParts()).contains("The title");
}
@Test
public void requestPartsWithCustomDescriptorAttributes() throws IOException {
TemplateResourceResolver resolver = mock(TemplateResourceResolver.class);
given(resolver.resolveTemplateResource("request-parts"))
.willReturn(snippetResource("request-parts-with-extra-column"));
@RenderedSnippetTest
@SnippetTemplate(snippet = "request-parts", template = "request-parts-with-extra-column")
void requestPartsWithCustomDescriptorAttributes(OperationBuilder operationBuilder, AssertableSnippets snippets)
throws IOException {
new RequestPartsSnippet(
Arrays.asList(partWithName("a").description("one").attributes(key("foo").value("alpha")),
partWithName("b").description("two").attributes(key("foo").value("bravo"))))
.document(this.operationBuilder
.attribute(TemplateEngine.class.getName(), new MustacheTemplateEngine(resolver))
.request("http://localhost")
.document(operationBuilder.request("http://localhost")
.part("a", "alpha".getBytes())
.and()
.part("b", "bravo".getBytes())
.build());
assertThat(this.generatedSnippets.requestParts())
.is(tableWithHeader("Part", "Description", "Foo").row("a", "one", "alpha").row("b", "two", "bravo"));
assertThat(snippets.requestParts()).isTable((table) -> table.withHeader("Part", "Description", "Foo")
.row("a", "one", "alpha")
.row("b", "two", "bravo"));
}
@Test
public void requestPartsWithOptionalColumn() throws IOException {
TemplateResourceResolver resolver = mock(TemplateResourceResolver.class);
given(resolver.resolveTemplateResource("request-parts"))
.willReturn(snippetResource("request-parts-with-optional-column"));
@RenderedSnippetTest
@SnippetTemplate(snippet = "request-parts", template = "request-parts-with-optional-column")
void requestPartsWithOptionalColumn(OperationBuilder operationBuilder, AssertableSnippets snippets)
throws IOException {
new RequestPartsSnippet(
Arrays.asList(partWithName("a").description("one").optional(), partWithName("b").description("two")))
.document(this.operationBuilder
.attribute(TemplateEngine.class.getName(), new MustacheTemplateEngine(resolver))
.request("http://localhost")
.document(operationBuilder.request("http://localhost")
.part("a", "alpha".getBytes())
.and()
.part("b", "bravo".getBytes())
.build());
assertThat(this.generatedSnippets.requestParts())
.is(tableWithHeader("Part", "Optional", "Description").row("a", "true", "one").row("b", "false", "two"));
assertThat(snippets.requestParts()).isTable((table) -> table.withHeader("Part", "Optional", "Description")
.row("a", "true", "one")
.row("b", "false", "two"));
}
@Test
public void additionalDescriptors() throws IOException {
@RenderedSnippetTest
void additionalDescriptors(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
RequestDocumentation.requestParts(partWithName("a").description("one"))
.and(partWithName("b").description("two"))
.document(this.operationBuilder.request("http://localhost")
.document(operationBuilder.request("http://localhost")
.part("a", "bravo".getBytes())
.and()
.part("b", "bravo".getBytes())
.build());
assertThat(this.generatedSnippets.requestParts())
.is(tableWithHeader("Part", "Description").row("`a`", "one").row("`b`", "two"));
assertThat(snippets.requestParts())
.isTable((table) -> table.withHeader("Part", "Description").row("`a`", "one").row("`b`", "two"));
}
@Test
public void requestPartsWithEscapedContent() throws IOException {
@RenderedSnippetTest
void requestPartsWithEscapedContent(OperationBuilder operationBuilder, AssertableSnippets snippets)
throws IOException {
RequestDocumentation.requestParts(partWithName("Foo|Bar").description("one|two"))
.document(this.operationBuilder.request("http://localhost").part("Foo|Bar", "baz".getBytes()).build());
assertThat(this.generatedSnippets.requestParts()).is(tableWithHeader("Part", "Description")
.row(escapeIfNecessary("`Foo|Bar`"), escapeIfNecessary("one|two")));
.document(operationBuilder.request("http://localhost").part("Foo|Bar", "baz".getBytes()).build());
assertThat(snippets.requestParts())
.isTable((table) -> table.withHeader("Part", "Description").row("`Foo|Bar`", "one|two"));
}
private String escapeIfNecessary(String input) {
if (this.templateFormat.getId().equals(TemplateFormats.markdown().getId())) {
return input;
}
return input.replace("|", "\\|");
@SnippetTest
void undocumentedPart(OperationBuilder operationBuilder) {
assertThatExceptionOfType(SnippetException.class)
.isThrownBy(() -> new RequestPartsSnippet(Collections.<RequestPartDescriptor>emptyList())
.document(operationBuilder.request("http://localhost").part("a", "alpha".getBytes()).build()))
.withMessage("Request parts with the following names were not documented: [a]");
}
@SnippetTest
void missingPart(OperationBuilder operationBuilder) {
assertThatExceptionOfType(SnippetException.class)
.isThrownBy(() -> new RequestPartsSnippet(Arrays.asList(partWithName("a").description("one")))
.document(operationBuilder.request("http://localhost").build()))
.withMessage("Request parts with the following names were not found in the request: [a]");
}
@SnippetTest
void undocumentedAndMissingParts(OperationBuilder operationBuilder) {
assertThatExceptionOfType(SnippetException.class)
.isThrownBy(() -> new RequestPartsSnippet(Arrays.asList(partWithName("a").description("one")))
.document(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-2023 the original author or authors.
* Copyright 2014-2025 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,7 +16,7 @@
package org.springframework.restdocs.snippet;
import org.junit.Test;
import org.junit.jupiter.api.Test;
import org.springframework.restdocs.ManualRestDocumentation;
import org.springframework.restdocs.RestDocumentationContext;
@@ -30,82 +30,82 @@ import static org.assertj.core.api.Assertions.assertThat;
* @author Andy Wilkinson
*
*/
public class RestDocumentationContextPlaceholderResolverTests {
class RestDocumentationContextPlaceholderResolverTests {
@Test
public void kebabCaseMethodName() {
void kebabCaseMethodName() {
assertThat(createResolver("dashSeparatedMethodName").resolvePlaceholder("method-name"))
.isEqualTo("dash-separated-method-name");
}
@Test
public void kebabCaseMethodNameWithUpperCaseOpeningSection() {
void kebabCaseMethodNameWithUpperCaseOpeningSection() {
assertThat(createResolver("URIDashSeparatedMethodName").resolvePlaceholder("method-name"))
.isEqualTo("uri-dash-separated-method-name");
}
@Test
public void kebabCaseMethodNameWithUpperCaseMidSection() {
void kebabCaseMethodNameWithUpperCaseMidSection() {
assertThat(createResolver("dashSeparatedMethodNameWithURIInIt").resolvePlaceholder("method-name"))
.isEqualTo("dash-separated-method-name-with-uri-in-it");
}
@Test
public void kebabCaseMethodNameWithUpperCaseEndSection() {
void kebabCaseMethodNameWithUpperCaseEndSection() {
assertThat(createResolver("dashSeparatedMethodNameWithURI").resolvePlaceholder("method-name"))
.isEqualTo("dash-separated-method-name-with-uri");
}
@Test
public void snakeCaseMethodName() {
void snakeCaseMethodName() {
assertThat(createResolver("underscoreSeparatedMethodName").resolvePlaceholder("method_name"))
.isEqualTo("underscore_separated_method_name");
}
@Test
public void snakeCaseMethodNameWithUpperCaseOpeningSection() {
void snakeCaseMethodNameWithUpperCaseOpeningSection() {
assertThat(createResolver("URIUnderscoreSeparatedMethodName").resolvePlaceholder("method_name"))
.isEqualTo("uri_underscore_separated_method_name");
}
@Test
public void snakeCaseMethodNameWithUpperCaseMidSection() {
void snakeCaseMethodNameWithUpperCaseMidSection() {
assertThat(createResolver("underscoreSeparatedMethodNameWithURIInIt").resolvePlaceholder("method_name"))
.isEqualTo("underscore_separated_method_name_with_uri_in_it");
}
@Test
public void snakeCaseMethodNameWithUpperCaseEndSection() {
void snakeCaseMethodNameWithUpperCaseEndSection() {
assertThat(createResolver("underscoreSeparatedMethodNameWithURI").resolvePlaceholder("method_name"))
.isEqualTo("underscore_separated_method_name_with_uri");
}
@Test
public void camelCaseMethodName() {
void camelCaseMethodName() {
assertThat(createResolver("camelCaseMethodName").resolvePlaceholder("methodName"))
.isEqualTo("camelCaseMethodName");
}
@Test
public void kebabCaseClassName() {
void kebabCaseClassName() {
assertThat(createResolver().resolvePlaceholder("class-name"))
.isEqualTo("rest-documentation-context-placeholder-resolver-tests");
}
@Test
public void snakeCaseClassName() {
void snakeCaseClassName() {
assertThat(createResolver().resolvePlaceholder("class_name"))
.isEqualTo("rest_documentation_context_placeholder_resolver_tests");
}
@Test
public void camelCaseClassName() {
void camelCaseClassName() {
assertThat(createResolver().resolvePlaceholder("ClassName"))
.isEqualTo("RestDocumentationContextPlaceholderResolverTests");
}
@Test
public void stepCount() {
void stepCount() {
assertThat(createResolver("stepCount").resolvePlaceholder("step")).isEqualTo("1");
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2023 the original author or authors.
* Copyright 2014-2025 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,9 +21,8 @@ import java.io.FileReader;
import java.io.IOException;
import java.io.Writer;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import org.springframework.restdocs.ManualRestDocumentation;
import org.springframework.restdocs.RestDocumentationContext;
@@ -40,10 +39,10 @@ import static org.mockito.Mockito.mock;
*
* @author Andy Wilkinson
*/
public class StandardWriterResolverTests {
class StandardWriterResolverTests {
@Rule
public final TemporaryFolder temp = new TemporaryFolder();
@TempDir
File temp;
private final PlaceholderResolverFactory placeholderResolverFactory = mock(PlaceholderResolverFactory.class);
@@ -51,21 +50,21 @@ public class StandardWriterResolverTests {
TemplateFormats.asciidoctor());
@Test
public void absoluteInput() {
void absoluteInput() {
String absolutePath = new File("foo").getAbsolutePath();
assertThat(this.resolver.resolveFile(absolutePath, "bar.txt", createContext(absolutePath)))
.isEqualTo(new File(absolutePath, "bar.txt"));
}
@Test
public void configuredOutputAndRelativeInput() {
void configuredOutputAndRelativeInput() {
File outputDir = new File("foo").getAbsoluteFile();
assertThat(this.resolver.resolveFile("bar", "baz.txt", createContext(outputDir.getAbsolutePath())))
.isEqualTo(new File(outputDir, "bar/baz.txt"));
}
@Test
public void configuredOutputAndAbsoluteInput() {
void configuredOutputAndAbsoluteInput() {
File outputDir = new File("foo").getAbsoluteFile();
String absolutePath = new File("bar").getAbsolutePath();
assertThat(this.resolver.resolveFile(absolutePath, "baz.txt", createContext(outputDir.getAbsolutePath())))
@@ -73,8 +72,8 @@ public class StandardWriterResolverTests {
}
@Test
public void placeholdersAreResolvedInOperationName() throws IOException {
File outputDirectory = this.temp.newFolder();
void placeholdersAreResolvedInOperationName() throws IOException {
File outputDirectory = this.temp;
RestDocumentationContext context = createContext(outputDirectory.getAbsolutePath());
PlaceholderResolver resolver = mock(PlaceholderResolver.class);
given(resolver.resolvePlaceholder("a")).willReturn("alpha");
@@ -84,8 +83,8 @@ public class StandardWriterResolverTests {
}
@Test
public void placeholdersAreResolvedInSnippetName() throws IOException {
File outputDirectory = this.temp.newFolder();
void placeholdersAreResolvedInSnippetName() throws IOException {
File outputDirectory = this.temp;
RestDocumentationContext context = createContext(outputDirectory.getAbsolutePath());
PlaceholderResolver resolver = mock(PlaceholderResolver.class);
given(resolver.resolvePlaceholder("b")).willReturn("bravo");

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2021 the original author or authors.
* Copyright 2014-2025 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,13 +21,12 @@ import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import org.junit.Rule;
import org.junit.Test;
import org.junit.jupiter.api.Test;
import org.springframework.restdocs.operation.Operation;
import org.springframework.restdocs.templates.TemplateFormats;
import org.springframework.restdocs.testfixtures.GeneratedSnippets;
import org.springframework.restdocs.testfixtures.OperationBuilder;
import org.springframework.restdocs.testfixtures.jupiter.AssertableSnippets;
import org.springframework.restdocs.testfixtures.jupiter.OperationBuilder;
import org.springframework.restdocs.testfixtures.jupiter.RenderedSnippetTest;
import static org.assertj.core.api.Assertions.assertThat;
@@ -36,16 +35,10 @@ import static org.assertj.core.api.Assertions.assertThat;
*
* @author Andy Wilkinson
*/
public class TemplatedSnippetTests {
@Rule
public OperationBuilder operationBuilder = new OperationBuilder(TemplateFormats.asciidoctor());
@Rule
public GeneratedSnippets snippets = new GeneratedSnippets(TemplateFormats.asciidoctor());
class TemplatedSnippetTests {
@Test
public void attributesAreCopied() {
void attributesAreCopied() {
Map<String, Object> attributes = new HashMap<>();
attributes.put("a", "alpha");
TemplatedSnippet snippet = new TestTemplatedSnippet(attributes);
@@ -55,22 +48,23 @@ public class TemplatedSnippetTests {
}
@Test
public void nullAttributesAreTolerated() {
void nullAttributesAreTolerated() {
assertThat(new TestTemplatedSnippet(null).getAttributes()).isNotNull();
assertThat(new TestTemplatedSnippet(null).getAttributes()).isEmpty();
}
@Test
public void snippetName() {
void snippetName() {
assertThat(new TestTemplatedSnippet(Collections.<String, Object>emptyMap()).getSnippetName()).isEqualTo("test");
}
@Test
public void multipleSnippetsCanBeProducedFromTheSameTemplate() throws IOException {
new TestTemplatedSnippet("one", "multiple-snippets").document(this.operationBuilder.build());
new TestTemplatedSnippet("two", "multiple-snippets").document(this.operationBuilder.build());
assertThat(this.snippets.snippet("multiple-snippets-one")).isNotNull();
assertThat(this.snippets.snippet("multiple-snippets-two")).isNotNull();
@RenderedSnippetTest
void multipleSnippetsCanBeProducedFromTheSameTemplate(OperationBuilder operationBuilder, AssertableSnippets snippet)
throws IOException {
new TestTemplatedSnippet("one", "multiple-snippets").document(operationBuilder.build());
new TestTemplatedSnippet("two", "multiple-snippets").document(operationBuilder.build());
assertThat(snippet.named("multiple-snippets-one")).exists();
assertThat(snippet.named("multiple-snippets-two")).exists();
}
private static class TestTemplatedSnippet extends TemplatedSnippet {

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2024 the original author or authors.
* Copyright 2014-2025 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,7 @@ import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.Callable;
import org.junit.Test;
import org.junit.jupiter.api.Test;
import org.springframework.core.io.Resource;
@@ -34,7 +34,7 @@ import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
*
* @author Andy Wilkinson
*/
public class StandardTemplateResourceResolverTests {
class StandardTemplateResourceResolverTests {
private final TemplateResourceResolver resolver = new StandardTemplateResourceResolver(
TemplateFormats.asciidoctor());
@@ -42,7 +42,7 @@ public class StandardTemplateResourceResolverTests {
private final TestClassLoader classLoader = new TestClassLoader();
@Test
public void formatSpecificCustomSnippetHasHighestPrecedence() throws IOException {
void formatSpecificCustomSnippetHasHighestPrecedence() throws IOException {
this.classLoader.addResource("org/springframework/restdocs/templates/asciidoctor/test.snippet",
getClass().getResource("test-format-specific-custom.snippet"));
this.classLoader.addResource("org/springframework/restdocs/templates/test.snippet",
@@ -62,7 +62,7 @@ public class StandardTemplateResourceResolverTests {
}
@Test
public void generalCustomSnippetIsUsedInAbsenceOfFormatSpecificCustomSnippet() throws IOException {
void generalCustomSnippetIsUsedInAbsenceOfFormatSpecificCustomSnippet() throws IOException {
this.classLoader.addResource("org/springframework/restdocs/templates/test.snippet",
getClass().getResource("test-custom.snippet"));
this.classLoader.addResource("org/springframework/restdocs/templates/asciidoctor/default-test.snippet",
@@ -80,7 +80,7 @@ public class StandardTemplateResourceResolverTests {
}
@Test
public void defaultSnippetIsUsedInAbsenceOfCustomSnippets() throws Exception {
void defaultSnippetIsUsedInAbsenceOfCustomSnippets() throws Exception {
this.classLoader.addResource("org/springframework/restdocs/templates/asciidoctor/default-test.snippet",
getClass().getResource("test-default.snippet"));
Resource snippet = doWithThreadContextClassLoader(this.classLoader, new Callable<Resource>() {
@@ -96,7 +96,7 @@ public class StandardTemplateResourceResolverTests {
}
@Test
public void failsIfCustomAndDefaultSnippetsDoNotExist() {
void failsIfCustomAndDefaultSnippetsDoNotExist() {
assertThatIllegalStateException()
.isThrownBy(() -> doWithThreadContextClassLoader(this.classLoader,
() -> StandardTemplateResourceResolverTests.this.resolver.resolveTemplateResource("test")))

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2018 the original author or authors.
* Copyright 2014-2025 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,7 +19,7 @@ package org.springframework.restdocs.templates.mustache;
import java.io.IOException;
import java.io.StringWriter;
import org.junit.Test;
import org.junit.jupiter.api.Test;
import org.springframework.restdocs.mustache.Template.Fragment;
@@ -32,10 +32,10 @@ import static org.mockito.Mockito.mock;
*
* @author Andy Wilkinson
*/
public class AsciidoctorTableCellContentLambdaTests {
class AsciidoctorTableCellContentLambdaTests {
@Test
public void verticalBarCharactersAreEscaped() throws IOException {
void verticalBarCharactersAreEscaped() throws IOException {
Fragment fragment = mock(Fragment.class);
given(fragment.execute()).willReturn("|foo|bar|baz|");
StringWriter writer = new StringWriter();
@@ -44,7 +44,7 @@ public class AsciidoctorTableCellContentLambdaTests {
}
@Test
public void escapedVerticalBarCharactersAreNotEscapedAgain() throws IOException {
void escapedVerticalBarCharactersAreNotEscapedAgain() throws IOException {
Fragment fragment = mock(Fragment.class);
given(fragment.execute()).willReturn("\\|foo|bar\\|baz|");
StringWriter writer = new StringWriter();

View File

@@ -1,4 +1,5 @@
{{title}}
Path | Type | Description
---- | ---- | -----------
{{#fields}}

View File

@@ -1,144 +0,0 @@
/*
* Copyright 2014-2023 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 java.io.File;
import java.io.FileInputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import org.junit.runners.model.Statement;
import org.springframework.restdocs.templates.TemplateFormat;
import org.springframework.util.FileCopyUtils;
import static org.assertj.core.api.Assertions.fail;
/**
* The {@code GeneratedSnippets} rule is used to capture the snippets generated by a test
* and assert their existence and content.
*
* @author Andy Wilkinson
* @author Andreas Evers
*/
public class GeneratedSnippets extends OperationTestRule {
private final TemplateFormat templateFormat;
private String operationName;
private File outputDirectory;
public GeneratedSnippets(TemplateFormat templateFormat) {
this.templateFormat = templateFormat;
}
@Override
public Statement apply(Statement base, File outputDirectory, String operationName) {
this.outputDirectory = outputDirectory;
this.operationName = operationName;
return base;
}
public String curlRequest() {
return snippet("curl-request");
}
public String httpieRequest() {
return snippet("httpie-request");
}
public String requestHeaders() {
return snippet("request-headers");
}
public String responseHeaders() {
return snippet("response-headers");
}
public String requestCookies() {
return snippet("request-cookies");
}
public String responseCookies() {
return snippet("response-cookies");
}
public String httpRequest() {
return snippet("http-request");
}
public String httpResponse() {
return snippet("http-response");
}
public String links() {
return snippet("links");
}
public String requestFields() {
return snippet("request-fields");
}
public String requestParts() {
return snippet("request-parts");
}
public String requestPartFields(String partName) {
return snippet("request-part-" + partName + "-fields");
}
public String responseFields() {
return snippet("response-fields");
}
public String pathParameters() {
return snippet("path-parameters");
}
public String queryParameters() {
return snippet("query-parameters");
}
public String formParameters() {
return snippet("form-parameters");
}
public String snippet(String name) {
File snippetFile = getSnippetFile(name);
try {
return FileCopyUtils
.copyToString(new InputStreamReader(new FileInputStream(snippetFile), StandardCharsets.UTF_8));
}
catch (Exception ex) {
fail("Failed to read '" + snippetFile + "'", ex);
return null;
}
}
private File getSnippetFile(String name) {
if (this.outputDirectory == null) {
fail("Output directory was null");
}
if (this.operationName == null) {
fail("Operation name was null");
}
File snippetDir = new File(this.outputDirectory, this.operationName);
return new File(snippetDir, name + "." + this.templateFormat.getFileExtension());
}
}

View File

@@ -1,52 +0,0 @@
/*
* Copyright 2014-2021 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 java.io.File;
import org.junit.rules.TestRule;
import org.junit.runner.Description;
import org.junit.runners.model.Statement;
/**
* Abstract base class for Operation-related {@link TestRule TestRules}.
*
* @author Andy Wilkinson
*/
abstract class OperationTestRule implements TestRule {
@Override
public final Statement apply(Statement base, Description description) {
return apply(base, determineOutputDirectory(description), determineOperationName(description));
}
private File determineOutputDirectory(Description description) {
return new File("build/" + description.getTestClass().getSimpleName());
}
private String determineOperationName(Description description) {
String operationName = description.getMethodName();
int index = operationName.indexOf('[');
if (index > 0) {
operationName = operationName.substring(0, index);
}
return operationName;
}
protected abstract Statement apply(Statement base, File outputDirectory, String operationName);
}

View File

@@ -1,86 +0,0 @@
/*
* 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();
}
}

View File

@@ -0,0 +1,679 @@
/*
* Copyright 2014-2025 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.jupiter;
import java.io.File;
import java.io.IOException;
import java.io.StringWriter;
import java.io.UncheckedIOException;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.function.UnaryOperator;
import org.assertj.core.api.AbstractStringAssert;
import org.assertj.core.api.AssertProvider;
import org.assertj.core.api.Assertions;
import org.springframework.restdocs.templates.TemplateFormat;
import org.springframework.restdocs.templates.TemplateFormats;
import org.springframework.util.StringUtils;
/**
* AssertJ {@link AssertProvider} for asserting that the generated snippets are correct.
*
* @author Andy Wilkinson
*/
public class AssertableSnippets {
private final File outputDirectory;
private final String operationName;
private final TemplateFormat templateFormat;
AssertableSnippets(File outputDirectory, String operationName, TemplateFormat templateFormat) {
this.outputDirectory = outputDirectory;
this.operationName = operationName;
this.templateFormat = templateFormat;
}
public File named(String name) {
return getSnippetFile(name);
}
private File getSnippetFile(String name) {
File snippetDir = new File(this.outputDirectory, this.operationName);
return new File(snippetDir, name + "." + this.templateFormat.getFileExtension());
}
public CodeBlockSnippetAssertProvider curlRequest() {
return new CodeBlockSnippetAssertProvider("curl-request");
}
public TableSnippetAssertProvider formParameters() {
return new TableSnippetAssertProvider("form-parameters");
}
public CodeBlockSnippetAssertProvider httpieRequest() {
return new CodeBlockSnippetAssertProvider("httpie-request");
}
public HttpRequestSnippetAssertProvider httpRequest() {
return new HttpRequestSnippetAssertProvider("http-request");
}
public HttpResponseSnippetAssertProvider httpResponse() {
return new HttpResponseSnippetAssertProvider("http-response");
}
public TableSnippetAssertProvider links() {
return new TableSnippetAssertProvider("links");
}
public TableSnippetAssertProvider pathParameters() {
return new TableSnippetAssertProvider("path-parameters");
}
public TableSnippetAssertProvider queryParameters() {
return new TableSnippetAssertProvider("query-parameters");
}
public CodeBlockSnippetAssertProvider requestBody() {
return new CodeBlockSnippetAssertProvider("request-body");
}
public CodeBlockSnippetAssertProvider requestBody(String suffix) {
return new CodeBlockSnippetAssertProvider("request-body-%s".formatted(suffix));
}
public TableSnippetAssertProvider requestCookies() {
return new TableSnippetAssertProvider("request-cookies");
}
public TableSnippetAssertProvider requestCookies(String suffix) {
return new TableSnippetAssertProvider("request-cookies-%s".formatted(suffix));
}
public TableSnippetAssertProvider requestFields() {
return new TableSnippetAssertProvider("request-fields");
}
public TableSnippetAssertProvider requestFields(String suffix) {
return new TableSnippetAssertProvider("request-fields-%s".formatted(suffix));
}
public TableSnippetAssertProvider requestHeaders() {
return new TableSnippetAssertProvider("request-headers");
}
public TableSnippetAssertProvider requestHeaders(String suffix) {
return new TableSnippetAssertProvider("request-headers-%s".formatted(suffix));
}
public CodeBlockSnippetAssertProvider requestPartBody(String partName) {
return new CodeBlockSnippetAssertProvider("request-part-%s-body".formatted(partName));
}
public CodeBlockSnippetAssertProvider requestPartBody(String partName, String suffix) {
return new CodeBlockSnippetAssertProvider("request-part-%s-body-%s".formatted(partName, suffix));
}
public TableSnippetAssertProvider requestPartFields(String partName) {
return new TableSnippetAssertProvider("request-part-%s-fields".formatted(partName));
}
public TableSnippetAssertProvider requestPartFields(String partName, String suffix) {
return new TableSnippetAssertProvider("request-part-%s-fields-%s".formatted(partName, suffix));
}
public TableSnippetAssertProvider requestParts() {
return new TableSnippetAssertProvider("request-parts");
}
public CodeBlockSnippetAssertProvider responseBody() {
return new CodeBlockSnippetAssertProvider("response-body");
}
public CodeBlockSnippetAssertProvider responseBody(String suffix) {
return new CodeBlockSnippetAssertProvider("response-body-%s".formatted(suffix));
}
public TableSnippetAssertProvider responseCookies() {
return new TableSnippetAssertProvider("response-cookies");
}
public TableSnippetAssertProvider responseFields() {
return new TableSnippetAssertProvider("response-fields");
}
public TableSnippetAssertProvider responseFields(String suffix) {
return new TableSnippetAssertProvider("response-fields-%s".formatted(suffix));
}
public TableSnippetAssertProvider responseHeaders() {
return new TableSnippetAssertProvider("response-headers");
}
public final class TableSnippetAssertProvider implements AssertProvider<TableSnippetAssert> {
private final String snippetName;
private TableSnippetAssertProvider(String snippetName) {
this.snippetName = snippetName;
}
@Override
public TableSnippetAssert assertThat() {
try {
String content = Files
.readString(new File(AssertableSnippets.this.outputDirectory, AssertableSnippets.this.operationName
+ "/" + this.snippetName + "." + AssertableSnippets.this.templateFormat.getFileExtension())
.toPath());
return new TableSnippetAssert(content);
}
catch (IOException ex) {
throw new UncheckedIOException(ex);
}
}
}
public final class TableSnippetAssert extends AbstractStringAssert<TableSnippetAssert> {
private TableSnippetAssert(String actual) {
super(actual, TableSnippetAssert.class);
}
public void isTable(UnaryOperator<Table<?>> tableOperator) {
Table<?> table = tableOperator
.apply(AssertableSnippets.this.templateFormat.equals(TemplateFormats.asciidoctor())
? new AsciidoctorTable() : new MarkdownTable());
table.getLinesAsString();
Assertions.assertThat(this.actual).isEqualTo(table.getLinesAsString());
}
}
public abstract class Table<T extends Table<T>> extends SnippetContent {
public abstract T withHeader(String... columns);
public abstract T withTitleAndHeader(String title, String... columns);
public abstract T row(String... entries);
public abstract T configuration(String string);
}
private final class AsciidoctorTable extends Table<AsciidoctorTable> {
@Override
public AsciidoctorTable withHeader(String... columns) {
return withTitleAndHeader("", columns);
}
@Override
public AsciidoctorTable withTitleAndHeader(String title, String... columns) {
if (!title.isBlank()) {
this.addLine("." + title);
}
this.addLine("|===");
String header = "|" + StringUtils.collectionToDelimitedString(Arrays.asList(columns), "|");
this.addLine(header);
this.addLine("");
this.addLine("|===");
return this;
}
@Override
public AsciidoctorTable row(String... entries) {
for (String entry : entries) {
this.addLine(-1, "|" + escapeEntry(entry));
}
this.addLine(-1, "");
return this;
}
private String escapeEntry(String entry) {
entry = entry.replace("|", "\\|");
if (entry.startsWith("`") && entry.endsWith("`")) {
return "`+" + entry.substring(1, entry.length() - 1) + "+`";
}
return entry;
}
@Override
public AsciidoctorTable configuration(String configuration) {
this.addLine(0, configuration);
return this;
}
}
private final class MarkdownTable extends Table<MarkdownTable> {
@Override
public MarkdownTable withHeader(String... columns) {
return withTitleAndHeader("", columns);
}
@Override
public MarkdownTable withTitleAndHeader(String title, String... columns) {
if (StringUtils.hasText(title)) {
this.addLine(title);
this.addLine("");
}
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("");
return this;
}
@Override
public MarkdownTable row(String... entries) {
this.addLine(-1, StringUtils.collectionToDelimitedString(Arrays.asList(entries), " | "));
return this;
}
@Override
public MarkdownTable configuration(String configuration) {
throw new UnsupportedOperationException("Markdown tables do not support configuration");
}
}
public final class CodeBlockSnippetAssertProvider implements AssertProvider<CodeBlockSnippetAssert> {
private final String snippetName;
private CodeBlockSnippetAssertProvider(String snippetName) {
this.snippetName = snippetName;
}
@Override
public CodeBlockSnippetAssert assertThat() {
try {
String content = Files
.readString(new File(AssertableSnippets.this.outputDirectory, AssertableSnippets.this.operationName
+ "/" + this.snippetName + "." + AssertableSnippets.this.templateFormat.getFileExtension())
.toPath());
return new CodeBlockSnippetAssert(content);
}
catch (IOException ex) {
throw new UncheckedIOException(ex);
}
}
}
public final class CodeBlockSnippetAssert extends AbstractStringAssert<CodeBlockSnippetAssert> {
private CodeBlockSnippetAssert(String actual) {
super(actual, CodeBlockSnippetAssert.class);
}
public void isCodeBlock(UnaryOperator<CodeBlock<?>> codeBlockOperator) {
CodeBlock<?> codeBlock = codeBlockOperator
.apply(AssertableSnippets.this.templateFormat.equals(TemplateFormats.asciidoctor())
? new AsciidoctorCodeBlock() : new MarkdownCodeBlock());
Assertions.assertThat(this.actual).isEqualTo(codeBlock.getLinesAsString());
}
}
public abstract class CodeBlock<T extends CodeBlock<T>> extends SnippetContent {
public abstract T withLanguage(String language);
public abstract T withOptions(String options);
public abstract T withLanguageAndOptions(String language, String options);
public abstract T content(String string);
}
private final class AsciidoctorCodeBlock extends CodeBlock<AsciidoctorCodeBlock> {
@Override
public AsciidoctorCodeBlock withLanguage(String language) {
addLine("[source,%s]".formatted(language));
return this;
}
@Override
public AsciidoctorCodeBlock withOptions(String options) {
addLine("[source,options=\"%s\"]".formatted(options));
return this;
}
@Override
public AsciidoctorCodeBlock withLanguageAndOptions(String language, String options) {
addLine("[source,%s,options=\"%s\"]".formatted(language, options));
return this;
}
@Override
public AsciidoctorCodeBlock content(String content) {
addLine("----");
addLine(content);
addLine("----");
return this;
}
}
private final class MarkdownCodeBlock extends CodeBlock<MarkdownCodeBlock> {
@Override
public MarkdownCodeBlock withLanguage(String language) {
addLine("```%s".formatted(language));
return this;
}
@Override
public MarkdownCodeBlock withOptions(String options) {
addLine("```");
return this;
}
@Override
public MarkdownCodeBlock withLanguageAndOptions(String language, String options) {
addLine("```%s".formatted(language));
return this;
}
@Override
public MarkdownCodeBlock content(String content) {
addLine(content);
addLine("```");
return this;
}
}
public final class HttpRequestSnippetAssertProvider implements AssertProvider<HttpRequestSnippetAssert> {
private final String snippetName;
private HttpRequestSnippetAssertProvider(String snippetName) {
this.snippetName = snippetName;
}
@Override
public HttpRequestSnippetAssert assertThat() {
try {
String content = Files
.readString(new File(AssertableSnippets.this.outputDirectory, AssertableSnippets.this.operationName
+ "/" + this.snippetName + "." + AssertableSnippets.this.templateFormat.getFileExtension())
.toPath());
return new HttpRequestSnippetAssert(content);
}
catch (IOException ex) {
throw new UncheckedIOException(ex);
}
}
}
public final class HttpRequestSnippetAssert extends AbstractStringAssert<HttpRequestSnippetAssert> {
private HttpRequestSnippetAssert(String actual) {
super(actual, HttpRequestSnippetAssert.class);
}
public void isHttpRequest(UnaryOperator<HttpRequest<?>> operator) {
HttpRequest<?> codeBlock = operator
.apply(AssertableSnippets.this.templateFormat.equals(TemplateFormats.asciidoctor())
? new AsciidoctorHttpRequest() : new MarkdownHttpRequest());
Assertions.assertThat(this.actual).isEqualTo(codeBlock.getLinesAsString());
}
}
public abstract class HttpRequest<T extends HttpRequest<T>> extends SnippetContent {
public T get(String uri) {
return request("GET", uri);
}
public T post(String uri) {
return request("POST", uri);
}
public T put(String uri) {
return request("PUT", uri);
}
public T patch(String uri) {
return request("PATCH", uri);
}
public T delete(String uri) {
return request("DELETE", uri);
}
protected abstract T request(String method, String uri);
public abstract T header(String name, Object value);
@SuppressWarnings("unchecked")
public T content(String content) {
addLine(-1, content);
return (T) this;
}
}
private final class AsciidoctorHttpRequest extends HttpRequest<AsciidoctorHttpRequest> {
private int headerOffset = 3;
@Override
protected AsciidoctorHttpRequest request(String method, String uri) {
addLine("[source,http,options=\"nowrap\"]");
addLine("----");
addLine("%s %s HTTP/1.1".formatted(method, uri));
addLine("");
addLine("----");
return this;
}
@Override
public AsciidoctorHttpRequest header(String name, Object value) {
addLine(this.headerOffset++, "%s: %s".formatted(name, value));
return this;
}
}
private final class MarkdownHttpRequest extends HttpRequest<MarkdownHttpRequest> {
private int headerOffset = 2;
@Override
public MarkdownHttpRequest request(String method, String uri) {
addLine("```http");
addLine("%s %s HTTP/1.1".formatted(method, uri));
addLine("");
addLine("```");
return this;
}
@Override
public MarkdownHttpRequest header(String name, Object value) {
addLine(this.headerOffset++, "%s: %s".formatted(name, value));
return this;
}
}
public final class HttpResponseSnippetAssertProvider implements AssertProvider<HttpResponseSnippetAssert> {
private final String snippetName;
private HttpResponseSnippetAssertProvider(String snippetName) {
this.snippetName = snippetName;
}
@Override
public HttpResponseSnippetAssert assertThat() {
try {
String content = Files
.readString(new File(AssertableSnippets.this.outputDirectory, AssertableSnippets.this.operationName
+ "/" + this.snippetName + "." + AssertableSnippets.this.templateFormat.getFileExtension())
.toPath());
return new HttpResponseSnippetAssert(content);
}
catch (IOException ex) {
throw new UncheckedIOException(ex);
}
}
}
public final class HttpResponseSnippetAssert extends AbstractStringAssert<HttpResponseSnippetAssert> {
private HttpResponseSnippetAssert(String actual) {
super(actual, HttpResponseSnippetAssert.class);
}
public void isHttpResponse(UnaryOperator<HttpResponse<?>> operator) {
HttpResponse<?> httpResponse = operator
.apply(AssertableSnippets.this.templateFormat.equals(TemplateFormats.asciidoctor())
? new AsciidoctorHttpResponse() : new MarkdownHttpResponse());
Assertions.assertThat(this.actual).isEqualTo(httpResponse.getLinesAsString());
}
}
public abstract class HttpResponse<T extends HttpResponse<T>> extends SnippetContent {
public T ok() {
return status("200 OK");
}
public T badRequest() {
return status("400 Bad Request");
}
public T status(int status) {
return status("%d ".formatted(status));
}
protected abstract T status(String status);
public abstract T header(String name, Object value);
@SuppressWarnings("unchecked")
public T content(String content) {
addLine(-1, content);
return (T) this;
}
}
private final class AsciidoctorHttpResponse extends HttpResponse<AsciidoctorHttpResponse> {
private int headerOffset = 3;
@Override
protected AsciidoctorHttpResponse status(String status) {
addLine("[source,http,options=\"nowrap\"]");
addLine("----");
addLine("HTTP/1.1 %s".formatted(status));
addLine("");
addLine("----");
return this;
}
@Override
public AsciidoctorHttpResponse header(String name, Object value) {
addLine(this.headerOffset++, "%s: %s".formatted(name, value));
return this;
}
}
private final class MarkdownHttpResponse extends HttpResponse<MarkdownHttpResponse> {
private int headerOffset = 2;
@Override
public MarkdownHttpResponse status(String status) {
addLine("```http");
addLine("HTTP/1.1 %s".formatted(status));
addLine("");
addLine("```");
return this;
}
@Override
public MarkdownHttpResponse header(String name, Object value) {
addLine(this.headerOffset++, "%s: %s".formatted(name, value));
return this;
}
}
private static class SnippetContent {
private List<String> lines = new ArrayList<>();
protected void addLine(String line) {
this.lines.add(line);
}
protected void addLine(int index, String line) {
this.lines.add(determineIndex(index), line);
}
private int determineIndex(int index) {
if (index >= 0) {
return index;
}
return index + this.lines.size();
}
protected String getLinesAsString() {
StringWriter writer = new StringWriter();
Iterator<String> iterator = this.lines.iterator();
while (iterator.hasNext()) {
writer.append(String.format("%s", iterator.next()));
if (iterator.hasNext()) {
writer.append(String.format("%n"));
}
}
return writer.toString();
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2022 the original author or authors.
* Copyright 2014-2025 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.
@@ -14,16 +14,11 @@
* limitations under the License.
*/
package org.springframework.restdocs.testfixtures;
package org.springframework.restdocs.testfixtures.jupiter;
/**
* 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>
* output that has been captured.
*
* @author Madhura Bhave
* @author Phillip Webb

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2022 the original author or authors.
* Copyright 2014-2025 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.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
package org.springframework.restdocs.testfixtures;
package org.springframework.restdocs.testfixtures.jupiter;
import java.io.File;
import java.net.URI;
@@ -26,8 +26,6 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
import org.junit.runners.model.Statement;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
@@ -59,21 +57,27 @@ import org.springframework.restdocs.templates.mustache.MustacheTemplateEngine;
*
* @author Andy Wilkinson
*/
public class OperationBuilder extends OperationTestRule {
public class OperationBuilder {
private final Map<String, Object> attributes = new HashMap<>();
private OperationResponseBuilder responseBuilder;
private final File outputDirectory;
private String name;
private File outputDirectory;
private final String name;
private final TemplateFormat templateFormat;
private OperationResponseBuilder responseBuilder;
private OperationRequestBuilder requestBuilder;
public OperationBuilder(TemplateFormat templateFormat) {
OperationBuilder(File outputDirectory, String name) {
this(outputDirectory, name, null);
}
OperationBuilder(File outputDirectory, String name, TemplateFormat templateFormat) {
this.outputDirectory = outputDirectory;
this.name = name;
this.templateFormat = templateFormat;
}
@@ -92,13 +96,6 @@ public class OperationBuilder extends OperationTestRule {
return this;
}
private void prepare(String operationName, File outputDirectory) {
this.name = operationName;
this.outputDirectory = outputDirectory;
this.requestBuilder = null;
this.attributes.clear();
}
public Operation build() {
if (this.attributes.get(TemplateEngine.class.getName()) == null) {
Map<String, Object> templateContext = new HashMap<>();
@@ -127,12 +124,6 @@ public class OperationBuilder extends OperationTestRule {
return context;
}
@Override
public Statement apply(Statement base, File outputDirectory, String operationName) {
prepare(operationName, outputDirectory);
return base;
}
/**
* Basic builder API for creating an {@link OperationRequest}.
*/

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2022 the original author or authors.
* Copyright 2014-2025 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.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
package org.springframework.restdocs.testfixtures;
package org.springframework.restdocs.testfixtures.jupiter;
import java.io.IOException;
import java.io.OutputStream;
@@ -35,8 +35,6 @@ import org.springframework.util.Assert;
* @author Madhura Bhave
* @author Phillip Webb
* @author Andy Wilkinson
* @author Sam Brannen
* @see OutputCaptureRule
*/
class OutputCapture implements CapturedOutput {
@@ -61,7 +59,7 @@ class OutputCapture implements CapturedOutput {
if (obj == this) {
return true;
}
if (obj instanceof CapturedOutput || obj instanceof CharSequence) {
if (obj instanceof CharSequence) {
return getAll().equals(obj.toString());
}
return false;
@@ -123,17 +121,17 @@ class OutputCapture implements CapturedOutput {
}
/**
* A capture session that captures {@link System#out System.out} and {@link System#out
* A capture session that captures {@link System#out System.out} and {@link System#err
* System.err}.
*/
private static class SystemCapture {
private final Object monitor = new Object();
private final PrintStreamCapture out;
private final PrintStreamCapture err;
private final Object monitor = new Object();
private final List<CapturedString> capturedStrings = new ArrayList<>();
SystemCapture() {
@@ -195,8 +193,8 @@ class OutputCapture implements CapturedOutput {
}
private static PrintStream getSystemStream(PrintStream printStream) {
while (printStream instanceof PrintStreamCapture) {
printStream = ((PrintStreamCapture) printStream).getParent();
while (printStream instanceof PrintStreamCapture printStreamCapture) {
printStream = printStreamCapture.getParent();
}
return printStream;
}

View File

@@ -0,0 +1,112 @@
/*
* Copyright 2014-2025 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.jupiter;
import org.junit.jupiter.api.extension.AfterAllCallback;
import org.junit.jupiter.api.extension.AfterEachCallback;
import org.junit.jupiter.api.extension.BeforeAllCallback;
import org.junit.jupiter.api.extension.BeforeEachCallback;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.jupiter.api.extension.ExtensionContext.Namespace;
import org.junit.jupiter.api.extension.ExtensionContext.Store;
import org.junit.jupiter.api.extension.ParameterContext;
import org.junit.jupiter.api.extension.ParameterResolutionException;
import org.junit.jupiter.api.extension.ParameterResolver;
/**
* JUnit Jupiter {@code @Extension} to capture {@link System#out System.out} and
* {@link System#err System.err}. Can be registered for an entire test class or for an
* individual test method through {@link ExtendWith @ExtendWith}. This extension provides
* {@linkplain ParameterResolver parameter resolution} for a {@link CapturedOutput}
* instance which can be used to assert that the correct output was written.
* <p>
* To use with {@link ExtendWith @ExtendWith}, inject the {@link CapturedOutput} as an
* argument to your test class constructor, test method, or lifecycle methods:
*
* <pre class="code">
* &#064;ExtendWith(OutputCaptureExtension.class)
* class MyTest {
*
* &#064;Test
* void test(CapturedOutput output) {
* System.out.println("ok");
* assertThat(output).contains("ok");
* System.err.println("error");
* }
*
* &#064;AfterEach
* void after(CapturedOutput output) {
* assertThat(output.getOut()).contains("ok");
* assertThat(output.getErr()).contains("error");
* }
*
* }
* </pre>
*
* @author Madhura Bhave
* @author Phillip Webb
* @author Andy Wilkinson
* @author Sam Brannen
* @see CapturedOutput
*/
public class OutputCaptureExtension
implements BeforeAllCallback, AfterAllCallback, BeforeEachCallback, AfterEachCallback, ParameterResolver {
OutputCaptureExtension() {
}
@Override
public void beforeAll(ExtensionContext context) throws Exception {
getOutputCapture(context).push();
}
@Override
public void afterAll(ExtensionContext context) throws Exception {
getOutputCapture(context).pop();
}
@Override
public void beforeEach(ExtensionContext context) throws Exception {
getOutputCapture(context).push();
}
@Override
public void afterEach(ExtensionContext context) throws Exception {
getOutputCapture(context).pop();
}
@Override
public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext)
throws ParameterResolutionException {
return CapturedOutput.class.equals(parameterContext.getParameter().getType());
}
@Override
public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) {
return getOutputCapture(extensionContext);
}
private OutputCapture getOutputCapture(ExtensionContext context) {
return getStore(context).getOrComputeIfAbsent(OutputCapture.class);
}
private Store getStore(ExtensionContext context) {
return context.getStore(Namespace.create(getClass()));
}
}

View File

@@ -0,0 +1,78 @@
/*
* Copyright 2014-2025 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.jupiter;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.junit.jupiter.api.TestTemplate;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.restdocs.templates.TemplateFormat;
import org.springframework.restdocs.templates.TemplateFormats;
/**
* Signals that a method is a template for a test that renders a snippet. The test will be
* executed once for each of the two supported snippet formats (Asciidoctor and Markdown).
* <p>
* A rendered snippet test method can inject the following types:
* <ul>
* <li>{@link OperationBuilder}</li>
* <li>{@link AssertableSnippets}</li>
* </ul>
*
* @author Andy Wilkinson
*/
@TestTemplate
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@ExtendWith(RenderedSnippetTestExtension.class)
public @interface RenderedSnippetTest {
/**
* The snippet formats to render.
* @return the formats
*/
Format[] format() default { Format.ASCIIDOCTOR, Format.MARKDOWN };
enum Format {
/**
* Asciidoctor snippet format.
*/
ASCIIDOCTOR(TemplateFormats.asciidoctor()),
/**
* Markdown snippet format.
*/
MARKDOWN(TemplateFormats.markdown());
private final TemplateFormat templateFormat;
Format(TemplateFormat templateFormat) {
this.templateFormat = templateFormat;
}
TemplateFormat templateFormat() {
return this.templateFormat;
}
}
}

View File

@@ -0,0 +1,155 @@
/*
* Copyright 2014-2025 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.jupiter;
import java.io.File;
import java.util.List;
import java.util.stream.Stream;
import org.junit.jupiter.api.extension.Extension;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.jupiter.api.extension.ExtensionContext.Namespace;
import org.junit.jupiter.api.extension.ExtensionContext.Store;
import org.junit.jupiter.api.extension.ParameterContext;
import org.junit.jupiter.api.extension.ParameterResolutionException;
import org.junit.jupiter.api.extension.ParameterResolver;
import org.junit.jupiter.api.extension.TestTemplateInvocationContext;
import org.junit.jupiter.api.extension.TestTemplateInvocationContextProvider;
import org.junit.platform.commons.util.AnnotationUtils;
import org.springframework.core.io.FileSystemResource;
import org.springframework.restdocs.templates.TemplateEngine;
import org.springframework.restdocs.templates.TemplateFormat;
import org.springframework.restdocs.templates.TemplateResourceResolver;
import org.springframework.restdocs.templates.mustache.MustacheTemplateEngine;
import org.springframework.restdocs.testfixtures.jupiter.RenderedSnippetTest.Format;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
/**
* {@link TestTemplateInvocationContextProvider} for
* {@link RenderedSnippetTest @RenderedSnippetTest} and
* {@link SnippetTemplate @SnippetTemplate}.
*
* @author Andy Wilkinson
*/
class RenderedSnippetTestExtension implements TestTemplateInvocationContextProvider {
@Override
public boolean supportsTestTemplate(ExtensionContext context) {
return true;
}
@Override
public Stream<TestTemplateInvocationContext> provideTestTemplateInvocationContexts(ExtensionContext context) {
return AnnotationUtils.findAnnotation(context.getRequiredTestMethod(), RenderedSnippetTest.class)
.map((renderedSnippetTest) -> Stream.of(renderedSnippetTest.format())
.map(Format::templateFormat)
.map(SnippetTestInvocationContext::new)
.map(TestTemplateInvocationContext.class::cast))
.orElseThrow();
}
static class SnippetTestInvocationContext implements TestTemplateInvocationContext {
private final TemplateFormat templateFormat;
SnippetTestInvocationContext(TemplateFormat templateFormat) {
this.templateFormat = templateFormat;
}
@Override
public List<Extension> getAdditionalExtensions() {
return List.of(new RenderedSnippetTestParameterResolver(this.templateFormat));
}
@Override
public String getDisplayName(int invocationIndex) {
return this.templateFormat.getId();
}
}
static class RenderedSnippetTestParameterResolver implements ParameterResolver {
private final TemplateFormat templateFormat;
RenderedSnippetTestParameterResolver(TemplateFormat templateFormat) {
this.templateFormat = templateFormat;
}
@Override
public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext)
throws ParameterResolutionException {
Class<?> parameterType = parameterContext.getParameter().getType();
return AssertableSnippets.class.equals(parameterType) || OperationBuilder.class.equals(parameterType)
|| TemplateFormat.class.equals(parameterType);
}
@Override
public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) {
Class<?> parameterType = parameterContext.getParameter().getType();
if (AssertableSnippets.class.equals(parameterType)) {
return getStore(extensionContext).getOrComputeIfAbsent(AssertableSnippets.class,
(key) -> new AssertableSnippets(determineOutputDirectory(extensionContext),
determineOperationName(extensionContext), this.templateFormat));
}
if (TemplateFormat.class.equals(parameterType)) {
return this.templateFormat;
}
return getStore(extensionContext).getOrComputeIfAbsent(OperationBuilder.class, (key) -> {
OperationBuilder operationBuilder = new OperationBuilder(determineOutputDirectory(extensionContext),
determineOperationName(extensionContext), this.templateFormat);
AnnotationUtils.findAnnotation(extensionContext.getRequiredTestMethod(), SnippetTemplate.class)
.ifPresent((snippetTemplate) -> {
TemplateResourceResolver resolver = mock(TemplateResourceResolver.class);
given(resolver.resolveTemplateResource(snippetTemplate.snippet()))
.willReturn(snippetResource(snippetTemplate.template(), this.templateFormat));
operationBuilder.attribute(TemplateEngine.class.getName(),
new MustacheTemplateEngine(resolver));
});
return operationBuilder;
});
}
private Store getStore(ExtensionContext extensionContext) {
return extensionContext.getStore(Namespace.create(getClass()));
}
private File determineOutputDirectory(ExtensionContext extensionContext) {
return new File("build/" + extensionContext.getRequiredTestClass().getSimpleName());
}
private String determineOperationName(ExtensionContext extensionContext) {
String operationName = extensionContext.getRequiredTestMethod().getName();
int index = operationName.indexOf('[');
if (index > 0) {
operationName = operationName.substring(0, index);
}
return operationName;
}
private FileSystemResource snippetResource(String name, TemplateFormat templateFormat) {
return new FileSystemResource(
"src/test/resources/custom-snippet-templates/" + templateFormat.getId() + "/" + name + ".snippet");
}
}
}

View File

@@ -0,0 +1,46 @@
/*
* Copyright 2014-2025 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.jupiter;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Customizes the template that will be used when rendering a snippet in a
* {@link RenderedSnippetTest rendered snippet test}.
*
* @author Andy Wilkinson
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface SnippetTemplate {
/**
* The name of the snippet whose template should be customized.
* @return the snippet name
*/
String snippet();
/**
* The custom template to use when rendering the snippet.
* @return the custom template
*/
String template();
}

View File

@@ -0,0 +1,47 @@
/*
* Copyright 2014-2025 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.jupiter;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.restdocs.snippet.Snippet;
/**
* Signals that a method is a test of a {@link Snippet}. Typically used to test scenarios
* where a failure occurs before the snippet is rendered. To test snippet rendering, use
* {@link RenderedSnippetTest}.
* <p>
* A snippet test method can inject the following types:
* <ul>
* <li>{@link OperationBuilder}</li>
* </ul>
*
* @author Andy Wilkinson
*/
@Test
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@ExtendWith(SnippetTestExtension.class)
public @interface SnippetTest {
}

View File

@@ -0,0 +1,66 @@
/*
* Copyright 2014-2025 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.jupiter;
import java.io.File;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.jupiter.api.extension.ExtensionContext.Namespace;
import org.junit.jupiter.api.extension.ExtensionContext.Store;
import org.junit.jupiter.api.extension.ParameterContext;
import org.junit.jupiter.api.extension.ParameterResolutionException;
import org.junit.jupiter.api.extension.ParameterResolver;
/**
* {@link ParameterResolver} for {@link SnippetTest @SnippetTest}.
*
* @author Andy Wilkinson
*/
class SnippetTestExtension implements ParameterResolver {
@Override
public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext)
throws ParameterResolutionException {
Class<?> parameterType = parameterContext.getParameter().getType();
return OperationBuilder.class.equals(parameterType);
}
@Override
public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) {
return getStore(extensionContext).getOrComputeIfAbsent(OperationBuilder.class,
(key) -> new OperationBuilder(determineOutputDirectory(extensionContext),
determineOperationName(extensionContext)));
}
private Store getStore(ExtensionContext extensionContext) {
return extensionContext.getStore(Namespace.create(getClass()));
}
private File determineOutputDirectory(ExtensionContext extensionContext) {
return new File("build/" + extensionContext.getRequiredTestClass().getSimpleName());
}
private String determineOperationName(ExtensionContext extensionContext) {
String operationName = extensionContext.getRequiredTestMethod().getName();
int index = operationName.indexOf('[');
if (index > 0) {
operationName = operationName.substring(0, index);
}
return operationName;
}
}

View File

@@ -16,8 +16,8 @@ dependencies {
internal(platform(project(":spring-restdocs-platform")))
testImplementation(testFixtures(project(":spring-restdocs-core")))
testImplementation("junit:junit")
testImplementation("org.assertj:assertj-core")
testImplementation("org.hamcrest:hamcrest-library")
testImplementation("org.mockito:mockito-core")
}
tasks.named("test") {
useJUnitPlatform()
}

View File

@@ -23,7 +23,7 @@ import java.util.Arrays;
import java.util.Iterator;
import jakarta.servlet.http.Part;
import org.junit.Test;
import org.junit.jupiter.api.Test;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
@@ -46,19 +46,19 @@ import static org.mockito.Mockito.mock;
*
* @author Andy Wilkinson
*/
public class MockMvcRequestConverterTests {
class MockMvcRequestConverterTests {
private final MockMvcRequestConverter factory = new MockMvcRequestConverter();
@Test
public void httpRequest() {
void httpRequest() {
OperationRequest request = createOperationRequest(MockMvcRequestBuilders.get("/foo"));
assertThat(request.getUri()).isEqualTo(URI.create("http://localhost/foo"));
assertThat(request.getMethod()).isEqualTo(HttpMethod.GET);
}
@Test
public void httpRequestWithCustomPort() {
void httpRequestWithCustomPort() {
MockHttpServletRequest mockRequest = MockMvcRequestBuilders.get("/foo").buildRequest(new MockServletContext());
mockRequest.setServerPort(8080);
OperationRequest request = this.factory.convert(mockRequest);
@@ -67,14 +67,14 @@ public class MockMvcRequestConverterTests {
}
@Test
public void requestWithContextPath() {
void requestWithContextPath() {
OperationRequest request = createOperationRequest(MockMvcRequestBuilders.get("/foo/bar").contextPath("/foo"));
assertThat(request.getUri()).isEqualTo(URI.create("http://localhost/foo/bar"));
assertThat(request.getMethod()).isEqualTo(HttpMethod.GET);
}
@Test
public void requestWithHeaders() {
void requestWithHeaders() {
OperationRequest request = createOperationRequest(
MockMvcRequestBuilders.get("/foo").header("a", "alpha", "apple").header("b", "bravo"));
assertThat(request.getUri()).isEqualTo(URI.create("http://localhost/foo"));
@@ -84,7 +84,7 @@ public class MockMvcRequestConverterTests {
}
@Test
public void requestWithCookies() {
void requestWithCookies() {
OperationRequest request = createOperationRequest(MockMvcRequestBuilders.get("/foo")
.cookie(new jakarta.servlet.http.Cookie("cookieName1", "cookieVal1"),
new jakarta.servlet.http.Cookie("cookieName2", "cookieVal2")));
@@ -104,7 +104,7 @@ public class MockMvcRequestConverterTests {
}
@Test
public void httpsRequest() {
void httpsRequest() {
MockHttpServletRequest mockRequest = MockMvcRequestBuilders.get("/foo").buildRequest(new MockServletContext());
mockRequest.setScheme("https");
mockRequest.setServerPort(443);
@@ -114,7 +114,7 @@ public class MockMvcRequestConverterTests {
}
@Test
public void httpsRequestWithCustomPort() {
void httpsRequestWithCustomPort() {
MockHttpServletRequest mockRequest = MockMvcRequestBuilders.get("/foo").buildRequest(new MockServletContext());
mockRequest.setScheme("https");
mockRequest.setServerPort(8443);
@@ -124,7 +124,7 @@ public class MockMvcRequestConverterTests {
}
@Test
public void getRequestWithParametersProducesUriWithQueryString() {
void getRequestWithParametersProducesUriWithQueryString() {
OperationRequest request = createOperationRequest(
MockMvcRequestBuilders.get("/foo").param("a", "alpha", "apple").param("b", "br&vo"));
assertThat(request.getUri()).isEqualTo(URI.create("http://localhost/foo?a=alpha&a=apple&b=br%26vo"));
@@ -132,7 +132,7 @@ public class MockMvcRequestConverterTests {
}
@Test
public void getRequestWithQueryString() {
void getRequestWithQueryString() {
MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.get("/foo?a=alpha&b=bravo");
OperationRequest request = createOperationRequest(builder);
assertThat(request.getUri()).isEqualTo(URI.create("http://localhost/foo?a=alpha&b=bravo"));
@@ -140,7 +140,7 @@ public class MockMvcRequestConverterTests {
}
@Test
public void postRequestWithParametersCreatesFormUrlEncodedContent() {
void postRequestWithParametersCreatesFormUrlEncodedContent() {
OperationRequest request = createOperationRequest(
MockMvcRequestBuilders.post("/foo").param("a", "alpha", "apple").param("b", "br&vo"));
assertThat(request.getUri()).isEqualTo(URI.create("http://localhost/foo"));
@@ -150,7 +150,7 @@ public class MockMvcRequestConverterTests {
}
@Test
public void postRequestWithParametersAndQueryStringCreatesFormUrlEncodedContentWithoutDuplication() {
void postRequestWithParametersAndQueryStringCreatesFormUrlEncodedContentWithoutDuplication() {
OperationRequest request = createOperationRequest(
MockMvcRequestBuilders.post("/foo?a=alpha").param("a", "apple").param("b", "br&vo"));
assertThat(request.getUri()).isEqualTo(URI.create("http://localhost/foo?a=alpha"));
@@ -160,7 +160,7 @@ public class MockMvcRequestConverterTests {
}
@Test
public void mockMultipartFileUpload() {
void mockMultipartFileUpload() {
OperationRequest request = createOperationRequest(MockMvcRequestBuilders.multipart("/foo")
.file(new MockMultipartFile("file", new byte[] { 1, 2, 3, 4 })));
assertThat(request.getUri()).isEqualTo(URI.create("http://localhost/foo"));
@@ -175,7 +175,7 @@ public class MockMvcRequestConverterTests {
}
@Test
public void mockMultipartFileUploadWithContentType() {
void mockMultipartFileUploadWithContentType() {
OperationRequest request = createOperationRequest(MockMvcRequestBuilders.multipart("/foo")
.file(new MockMultipartFile("file", "original", "image/png", new byte[] { 1, 2, 3, 4 })));
assertThat(request.getUri()).isEqualTo(URI.create("http://localhost/foo"));
@@ -189,7 +189,7 @@ public class MockMvcRequestConverterTests {
}
@Test
public void requestWithPart() throws IOException {
void requestWithPart() throws IOException {
MockHttpServletRequest mockRequest = MockMvcRequestBuilders.get("/foo").buildRequest(new MockServletContext());
Part mockPart = mock(Part.class);
given(mockPart.getHeaderNames()).willReturn(Arrays.asList("a", "b"));
@@ -211,7 +211,7 @@ public class MockMvcRequestConverterTests {
}
@Test
public void requestWithPartWithContentType() throws IOException {
void requestWithPartWithContentType() throws IOException {
MockHttpServletRequest mockRequest = MockMvcRequestBuilders.get("/foo").buildRequest(new MockServletContext());
Part mockPart = mock(Part.class);
given(mockPart.getHeaderNames()).willReturn(Arrays.asList("a", "b"));

View File

@@ -20,7 +20,7 @@ import java.util.Collections;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletResponse;
import org.junit.Test;
import org.junit.jupiter.api.Test;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
@@ -37,12 +37,12 @@ import static org.assertj.core.api.Assertions.entry;
*
* @author Tomasz Kopczynski
*/
public class MockMvcResponseConverterTests {
class MockMvcResponseConverterTests {
private final MockMvcResponseConverter factory = new MockMvcResponseConverter();
@Test
public void basicResponse() {
void basicResponse() {
MockHttpServletResponse response = new MockHttpServletResponse();
response.setStatus(HttpServletResponse.SC_OK);
OperationResponse operationResponse = this.factory.convert(response);
@@ -50,7 +50,7 @@ public class MockMvcResponseConverterTests {
}
@Test
public void responseWithCookie() {
void responseWithCookie() {
MockHttpServletResponse response = new MockHttpServletResponse();
response.setStatus(HttpServletResponse.SC_OK);
Cookie cookie = new Cookie("name", "value");
@@ -66,7 +66,7 @@ public class MockMvcResponseConverterTests {
}
@Test
public void responseWithCustomStatus() {
void responseWithCustomStatus() {
MockHttpServletResponse response = new MockHttpServletResponse();
response.setStatus(600);
OperationResponse operationResponse = this.factory.convert(response);

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2023 the original author or authors.
* Copyright 2014-2025 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,12 +19,13 @@ package org.springframework.restdocs.mockmvc;
import java.lang.reflect.Method;
import java.util.Map;
import org.junit.Assume;
import org.junit.Rule;
import org.junit.Test;
import org.junit.jupiter.api.Assumptions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.restdocs.JUnitRestDocumentation;
import org.springframework.restdocs.RestDocumentationContextProvider;
import org.springframework.restdocs.RestDocumentationExtension;
import org.springframework.restdocs.generate.RestDocumentationGenerator;
import org.springframework.test.web.servlet.request.RequestPostProcessor;
import org.springframework.util.ReflectionUtils;
@@ -41,24 +42,22 @@ import static org.assertj.core.api.Assertions.assertThat;
* @author Andy Wilkinson
* @author Dmitriy Mayboroda
*/
public class MockMvcRestDocumentationConfigurerTests {
@ExtendWith(RestDocumentationExtension.class)
class MockMvcRestDocumentationConfigurerTests {
private MockHttpServletRequest request = new MockHttpServletRequest();
@Rule
public JUnitRestDocumentation restDocumentation = new JUnitRestDocumentation();
@Test
public void defaultConfiguration() {
RequestPostProcessor postProcessor = new MockMvcRestDocumentationConfigurer(this.restDocumentation)
void defaultConfiguration(RestDocumentationContextProvider restDocumentation) {
RequestPostProcessor postProcessor = new MockMvcRestDocumentationConfigurer(restDocumentation)
.beforeMockMvcCreated(null, null);
postProcessor.postProcessRequest(this.request);
assertUriConfiguration("http", "localhost", 8080);
}
@Test
public void customScheme() {
RequestPostProcessor postProcessor = new MockMvcRestDocumentationConfigurer(this.restDocumentation).uris()
void customScheme(RestDocumentationContextProvider restDocumentation) {
RequestPostProcessor postProcessor = new MockMvcRestDocumentationConfigurer(restDocumentation).uris()
.withScheme("https")
.beforeMockMvcCreated(null, null);
postProcessor.postProcessRequest(this.request);
@@ -66,8 +65,8 @@ public class MockMvcRestDocumentationConfigurerTests {
}
@Test
public void customHost() {
RequestPostProcessor postProcessor = new MockMvcRestDocumentationConfigurer(this.restDocumentation).uris()
void customHost(RestDocumentationContextProvider restDocumentation) {
RequestPostProcessor postProcessor = new MockMvcRestDocumentationConfigurer(restDocumentation).uris()
.withHost("api.example.com")
.beforeMockMvcCreated(null, null);
postProcessor.postProcessRequest(this.request);
@@ -75,8 +74,8 @@ public class MockMvcRestDocumentationConfigurerTests {
}
@Test
public void customPort() {
RequestPostProcessor postProcessor = new MockMvcRestDocumentationConfigurer(this.restDocumentation).uris()
void customPort(RestDocumentationContextProvider restDocumentation) {
RequestPostProcessor postProcessor = new MockMvcRestDocumentationConfigurer(restDocumentation).uris()
.withPort(8081)
.beforeMockMvcCreated(null, null);
postProcessor.postProcessRequest(this.request);
@@ -84,8 +83,8 @@ public class MockMvcRestDocumentationConfigurerTests {
}
@Test
public void noContentLengthHeaderWhenRequestHasNotContent() {
RequestPostProcessor postProcessor = new MockMvcRestDocumentationConfigurer(this.restDocumentation).uris()
void noContentLengthHeaderWhenRequestHasNotContent(RestDocumentationContextProvider restDocumentation) {
RequestPostProcessor postProcessor = new MockMvcRestDocumentationConfigurer(restDocumentation).uris()
.withPort(8081)
.beforeMockMvcCreated(null, null);
postProcessor.postProcessRequest(this.request);
@@ -94,8 +93,8 @@ public class MockMvcRestDocumentationConfigurerTests {
@Test
@SuppressWarnings("unchecked")
public void uriTemplateFromRequestAttribute() {
RequestPostProcessor postProcessor = new MockMvcRestDocumentationConfigurer(this.restDocumentation)
void uriTemplateFromRequestAttribute(RestDocumentationContextProvider restDocumentation) {
RequestPostProcessor postProcessor = new MockMvcRestDocumentationConfigurer(restDocumentation)
.beforeMockMvcCreated(null, null);
this.request.setAttribute(RestDocumentationGenerator.ATTRIBUTE_NAME_URL_TEMPLATE, "{a}/{b}");
postProcessor.postProcessRequest(this.request);
@@ -106,11 +105,11 @@ public class MockMvcRestDocumentationConfigurerTests {
@Test
@SuppressWarnings("unchecked")
public void uriTemplateFromRequest() {
void uriTemplateFromRequest(RestDocumentationContextProvider restDocumentation) {
Method setUriTemplate = ReflectionUtils.findMethod(MockHttpServletRequest.class, "setUriTemplate",
String.class);
Assume.assumeNotNull(setUriTemplate);
RequestPostProcessor postProcessor = new MockMvcRestDocumentationConfigurer(this.restDocumentation)
Assumptions.assumeFalse(setUriTemplate == null);
RequestPostProcessor postProcessor = new MockMvcRestDocumentationConfigurer(restDocumentation)
.beforeMockMvcCreated(null, null);
ReflectionUtils.invokeMethod(setUriTemplate, this.request, "{a}/{b}");
postProcessor.postProcessRequest(this.request);

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2024 the original author or authors.
* Copyright 2014-2025 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.
@@ -34,11 +34,10 @@ import java.util.regex.Pattern;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletResponse;
import org.assertj.core.api.Condition;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
@@ -47,7 +46,8 @@ import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.restdocs.JUnitRestDocumentation;
import org.springframework.restdocs.RestDocumentationContextProvider;
import org.springframework.restdocs.RestDocumentationExtension;
import org.springframework.restdocs.mockmvc.MockMvcRestDocumentationIntegrationTests.TestConfiguration;
import org.springframework.restdocs.templates.TemplateFormat;
import org.springframework.restdocs.templates.TemplateFormats;
@@ -56,7 +56,7 @@ import org.springframework.restdocs.testfixtures.SnippetConditions.CodeBlockCond
import org.springframework.restdocs.testfixtures.SnippetConditions.HttpRequestCondition;
import org.springframework.restdocs.testfixtures.SnippetConditions.HttpResponseCondition;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
@@ -106,29 +106,30 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
* @author Tomasz Kopczynski
* @author Filip Hrisafov
*/
@RunWith(SpringJUnit4ClassRunner.class)
@SpringJUnitConfig
@WebAppConfiguration
@ExtendWith(RestDocumentationExtension.class)
@ContextConfiguration(classes = TestConfiguration.class)
public class MockMvcRestDocumentationIntegrationTests {
@Rule
public JUnitRestDocumentation restDocumentation = new JUnitRestDocumentation();
private RestDocumentationContextProvider restDocumentation;
@Autowired
private WebApplicationContext context;
@Before
public void deleteSnippets() {
@BeforeEach
void setUp(RestDocumentationContextProvider restDocumentation) {
this.restDocumentation = restDocumentation;
FileSystemUtils.deleteRecursively(new File("build/generated-snippets"));
}
@After
public void clearOutputDirSystemProperty() {
@AfterEach
void clearOutputDirSystemProperty() {
System.clearProperty("org.springframework.restdocs.outputDir");
}
@Test
public void basicSnippetGeneration() throws Exception {
void basicSnippetGeneration() throws Exception {
MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.context)
.apply(new MockMvcRestDocumentationConfigurer(this.restDocumentation).snippets().withEncoding("UTF-8"))
.build();
@@ -140,7 +141,19 @@ public class MockMvcRestDocumentationIntegrationTests {
}
@Test
public void markdownSnippetGeneration() throws Exception {
void getRequestWithBody() throws Exception {
MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.context)
.apply(new MockMvcRestDocumentationConfigurer(this.restDocumentation).snippets().withEncoding("UTF-8"))
.build();
mockMvc.perform(get("/").accept(MediaType.APPLICATION_JSON).content("some body content"))
.andExpect(status().isOk())
.andDo(document("get-request-with-body"));
assertExpectedSnippetFilesExist(new File("build/generated-snippets/get-request-with-body"), "http-request.adoc",
"http-response.adoc", "curl-request.adoc");
}
@Test
void markdownSnippetGeneration() throws Exception {
MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.context)
.apply(new MockMvcRestDocumentationConfigurer(this.restDocumentation).snippets()
.withEncoding("UTF-8")
@@ -154,7 +167,7 @@ public class MockMvcRestDocumentationIntegrationTests {
}
@Test
public void curlSnippetWithContent() throws Exception {
void curlSnippetWithContent() throws Exception {
MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.context)
.apply(documentationConfiguration(this.restDocumentation))
.build();
@@ -168,7 +181,7 @@ public class MockMvcRestDocumentationIntegrationTests {
}
@Test
public void curlSnippetWithCookies() throws Exception {
void curlSnippetWithCookies() throws Exception {
MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.context)
.apply(documentationConfiguration(this.restDocumentation))
.build();
@@ -182,7 +195,7 @@ public class MockMvcRestDocumentationIntegrationTests {
}
@Test
public void curlSnippetWithQueryStringOnPost() throws Exception {
void curlSnippetWithQueryStringOnPost() throws Exception {
MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.context)
.apply(documentationConfiguration(this.restDocumentation))
.build();
@@ -196,7 +209,7 @@ public class MockMvcRestDocumentationIntegrationTests {
}
@Test
public void curlSnippetWithEmptyParameterQueryString() throws Exception {
void curlSnippetWithEmptyParameterQueryString() throws Exception {
MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.context)
.apply(documentationConfiguration(this.restDocumentation))
.build();
@@ -210,7 +223,7 @@ public class MockMvcRestDocumentationIntegrationTests {
}
@Test
public void curlSnippetWithContentAndParametersOnPost() throws Exception {
void curlSnippetWithContentAndParametersOnPost() throws Exception {
MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.context)
.apply(documentationConfiguration(this.restDocumentation))
.build();
@@ -224,7 +237,7 @@ public class MockMvcRestDocumentationIntegrationTests {
}
@Test
public void httpieSnippetWithContent() throws Exception {
void httpieSnippetWithContent() throws Exception {
MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.context)
.apply(documentationConfiguration(this.restDocumentation))
.build();
@@ -237,7 +250,7 @@ public class MockMvcRestDocumentationIntegrationTests {
}
@Test
public void httpieSnippetWithCookies() throws Exception {
void httpieSnippetWithCookies() throws Exception {
MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.context)
.apply(documentationConfiguration(this.restDocumentation))
.build();
@@ -251,7 +264,7 @@ public class MockMvcRestDocumentationIntegrationTests {
}
@Test
public void httpieSnippetWithQueryStringOnPost() throws Exception {
void httpieSnippetWithQueryStringOnPost() throws Exception {
MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.context)
.apply(documentationConfiguration(this.restDocumentation))
.build();
@@ -265,7 +278,7 @@ public class MockMvcRestDocumentationIntegrationTests {
}
@Test
public void httpieSnippetWithContentAndParametersOnPost() throws Exception {
void httpieSnippetWithContentAndParametersOnPost() throws Exception {
MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.context)
.apply(documentationConfiguration(this.restDocumentation))
.build();
@@ -280,7 +293,7 @@ public class MockMvcRestDocumentationIntegrationTests {
}
@Test
public void linksSnippet() throws Exception {
void linksSnippet() throws Exception {
MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.context)
.apply(documentationConfiguration(this.restDocumentation))
.build();
@@ -293,7 +306,7 @@ public class MockMvcRestDocumentationIntegrationTests {
}
@Test
public void pathParametersSnippet() throws Exception {
void pathParametersSnippet() throws Exception {
MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.context)
.apply(documentationConfiguration(this.restDocumentation))
.build();
@@ -305,7 +318,7 @@ public class MockMvcRestDocumentationIntegrationTests {
}
@Test
public void queryParametersSnippet() throws Exception {
void queryParametersSnippet() throws Exception {
MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.context)
.apply(documentationConfiguration(this.restDocumentation))
.build();
@@ -317,7 +330,7 @@ public class MockMvcRestDocumentationIntegrationTests {
}
@Test
public void requestFieldsSnippet() throws Exception {
void requestFieldsSnippet() throws Exception {
MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.context)
.apply(documentationConfiguration(this.restDocumentation))
.build();
@@ -329,7 +342,7 @@ public class MockMvcRestDocumentationIntegrationTests {
}
@Test
public void requestPartsSnippet() throws Exception {
void requestPartsSnippet() throws Exception {
MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.context)
.apply(documentationConfiguration(this.restDocumentation))
.build();
@@ -341,7 +354,7 @@ public class MockMvcRestDocumentationIntegrationTests {
}
@Test
public void responseFieldsSnippet() throws Exception {
void responseFieldsSnippet() throws Exception {
MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.context)
.apply(documentationConfiguration(this.restDocumentation))
.build();
@@ -354,7 +367,7 @@ public class MockMvcRestDocumentationIntegrationTests {
}
@Test
public void responseWithSetCookie() throws Exception {
void responseWithSetCookie() throws Exception {
MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.context)
.apply(documentationConfiguration(this.restDocumentation))
.build();
@@ -368,7 +381,7 @@ public class MockMvcRestDocumentationIntegrationTests {
}
@Test
public void parameterizedOutputDirectory() throws Exception {
void parameterizedOutputDirectory() throws Exception {
MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.context)
.apply(documentationConfiguration(this.restDocumentation))
.build();
@@ -380,7 +393,7 @@ public class MockMvcRestDocumentationIntegrationTests {
}
@Test
public void multiStep() throws Exception {
void multiStep() throws Exception {
MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.context)
.apply(documentationConfiguration(this.restDocumentation))
.alwaysDo(document("{method-name}-{step}"))
@@ -398,7 +411,7 @@ public class MockMvcRestDocumentationIntegrationTests {
}
@Test
public void alwaysDoWithAdditionalSnippets() throws Exception {
void alwaysDoWithAdditionalSnippets() throws Exception {
RestDocumentationResultHandler documentation = document("{method-name}-{step}");
MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.context)
.apply(documentationConfiguration(this.restDocumentation))
@@ -412,7 +425,7 @@ public class MockMvcRestDocumentationIntegrationTests {
}
@Test
public void preprocessedRequest() throws Exception {
void preprocessedRequest() throws Exception {
MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.context)
.apply(documentationConfiguration(this.restDocumentation))
.build();
@@ -455,7 +468,7 @@ public class MockMvcRestDocumentationIntegrationTests {
}
@Test
public void defaultPreprocessedRequest() throws Exception {
void defaultPreprocessedRequest() throws Exception {
Pattern pattern = Pattern.compile("(\"alpha\")");
MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.context)
.apply(documentationConfiguration(this.restDocumentation).operationPreprocessors()
@@ -486,7 +499,7 @@ public class MockMvcRestDocumentationIntegrationTests {
}
@Test
public void preprocessedResponse() throws Exception {
void preprocessedResponse() throws Exception {
MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.context)
.apply(documentationConfiguration(this.restDocumentation))
.build();
@@ -515,7 +528,7 @@ public class MockMvcRestDocumentationIntegrationTests {
}
@Test
public void defaultPreprocessedResponse() throws Exception {
void defaultPreprocessedResponse() throws Exception {
Pattern pattern = Pattern.compile("(\"alpha\")");
MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.context)
.apply(documentationConfiguration(this.restDocumentation).operationPreprocessors()
@@ -537,7 +550,7 @@ public class MockMvcRestDocumentationIntegrationTests {
}
@Test
public void customSnippetTemplate() throws Exception {
void customSnippetTemplate() throws Exception {
MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.context)
.apply(documentationConfiguration(this.restDocumentation))
.build();
@@ -559,7 +572,7 @@ public class MockMvcRestDocumentationIntegrationTests {
}
@Test
public void customContextPath() throws Exception {
void customContextPath() throws Exception {
MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.context)
.apply(documentationConfiguration(this.restDocumentation))
.build();
@@ -573,7 +586,7 @@ public class MockMvcRestDocumentationIntegrationTests {
}
@Test
public void exceptionShouldBeThrownWhenCallDocumentMockMvcNotConfigured() {
void exceptionShouldBeThrownWhenCallDocumentMockMvcNotConfigured() {
MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.context).build();
assertThatThrownBy(() -> mockMvc.perform(get("/").accept(MediaType.APPLICATION_JSON)).andDo(document("basic")))
.isInstanceOf(IllegalStateException.class)
@@ -583,7 +596,7 @@ public class MockMvcRestDocumentationIntegrationTests {
}
@Test
public void exceptionShouldBeThrownWhenCallDocumentSnippetsMockMvcNotConfigured() {
void exceptionShouldBeThrownWhenCallDocumentSnippetsMockMvcNotConfigured() {
RestDocumentationResultHandler documentation = document("{method-name}-{step}");
MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.context).build();
assertThatThrownBy(() -> mockMvc.perform(get("/").accept(MediaType.APPLICATION_JSON))
@@ -594,7 +607,7 @@ public class MockMvcRestDocumentationIntegrationTests {
}
@Test
public void multiPart() throws Exception {
void multiPart() throws Exception {
MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.context)
.apply(documentationConfiguration(this.restDocumentation))
.build();

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2023 the original author or authors.
* Copyright 2014-2025 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,7 +19,7 @@ package org.springframework.restdocs.mockmvc;
import java.net.URI;
import jakarta.servlet.ServletContext;
import org.junit.Test;
import org.junit.jupiter.api.Test;
import org.springframework.http.HttpMethod;
import org.springframework.mock.web.MockHttpServletRequest;
@@ -44,97 +44,97 @@ import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuild
* @author Andy Wilkinson
*
*/
public class RestDocumentationRequestBuildersTests {
class RestDocumentationRequestBuildersTests {
private final ServletContext servletContext = new MockServletContext();
@Test
public void getTemplate() {
void getTemplate() {
assertTemplate(get("/{template}", "t"), HttpMethod.GET);
}
@Test
public void getUri() {
void getUri() {
assertUri(get(URI.create("/uri")), HttpMethod.GET);
}
@Test
public void postTemplate() {
void postTemplate() {
assertTemplate(post("/{template}", "t"), HttpMethod.POST);
}
@Test
public void postUri() {
void postUri() {
assertUri(post(URI.create("/uri")), HttpMethod.POST);
}
@Test
public void putTemplate() {
void putTemplate() {
assertTemplate(put("/{template}", "t"), HttpMethod.PUT);
}
@Test
public void putUri() {
void putUri() {
assertUri(put(URI.create("/uri")), HttpMethod.PUT);
}
@Test
public void patchTemplate() {
void patchTemplate() {
assertTemplate(patch("/{template}", "t"), HttpMethod.PATCH);
}
@Test
public void patchUri() {
void patchUri() {
assertUri(patch(URI.create("/uri")), HttpMethod.PATCH);
}
@Test
public void deleteTemplate() {
void deleteTemplate() {
assertTemplate(delete("/{template}", "t"), HttpMethod.DELETE);
}
@Test
public void deleteUri() {
void deleteUri() {
assertUri(delete(URI.create("/uri")), HttpMethod.DELETE);
}
@Test
public void optionsTemplate() {
void optionsTemplate() {
assertTemplate(options("/{template}", "t"), HttpMethod.OPTIONS);
}
@Test
public void optionsUri() {
void optionsUri() {
assertUri(options(URI.create("/uri")), HttpMethod.OPTIONS);
}
@Test
public void headTemplate() {
void headTemplate() {
assertTemplate(head("/{template}", "t"), HttpMethod.HEAD);
}
@Test
public void headUri() {
void headUri() {
assertUri(head(URI.create("/uri")), HttpMethod.HEAD);
}
@Test
public void requestTemplate() {
void requestTemplate() {
assertTemplate(request(HttpMethod.GET, "/{template}", "t"), HttpMethod.GET);
}
@Test
public void requestUri() {
void requestUri() {
assertUri(request(HttpMethod.GET, URI.create("/uri")), HttpMethod.GET);
}
@Test
public void multipartTemplate() {
void multipartTemplate() {
assertTemplate(multipart("/{template}", "t"), HttpMethod.POST);
}
@Test
public void multipartUri() {
void multipartUri() {
assertUri(multipart(URI.create("/uri")), HttpMethod.POST);
}

View File

@@ -15,6 +15,7 @@ dependencies {
api("org.apache.pdfbox:pdfbox:2.0.27")
api("org.apache.tomcat.embed:tomcat-embed-core:11.0.2")
api("org.apache.tomcat.embed:tomcat-embed-el:11.0.2")
api("org.apiguardian:apiguardian-api:1.1.2")
api("org.asciidoctor:asciidoctorj:3.0.0")
api("org.asciidoctor:asciidoctorj-pdf:2.3.19")
api("org.assertj:assertj-core:3.23.1")
@@ -22,10 +23,10 @@ dependencies {
api("org.hamcrest:hamcrest-library:1.3")
api("org.hibernate.validator:hibernate-validator:9.0.0.CR1")
api("org.javamoney:moneta:1.4.2")
api("org.junit.jupiter:junit-jupiter-api:5.0.0")
}
api(enforcedPlatform("com.fasterxml.jackson:jackson-bom:2.14.0"))
api(enforcedPlatform("io.rest-assured:rest-assured-bom:5.2.1"))
api(enforcedPlatform("org.mockito:mockito-bom:4.9.0"))
api(enforcedPlatform("org.junit:junit-bom:5.13.0"))
api(enforcedPlatform("org.springframework:spring-framework-bom:$springFrameworkVersion"))
}

View File

@@ -13,13 +13,14 @@ dependencies {
internal(platform(project(":spring-restdocs-platform")))
testCompileOnly("org.apiguardian:apiguardian-api")
testImplementation(testFixtures(project(":spring-restdocs-core")))
testImplementation("com.fasterxml.jackson.core:jackson-databind")
testImplementation("junit:junit")
testImplementation("org.apache.tomcat.embed:tomcat-embed-core")
testImplementation("org.assertj:assertj-core")
testImplementation("org.hamcrest:hamcrest-library")
testImplementation("org.mockito:mockito-core")
}
tasks.named("test") {
useJUnitPlatform();
}
compatibilityTest {

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2023 the original author or authors.
* Copyright 2014-2025 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,8 +19,8 @@ package org.springframework.restdocs.restassured;
import io.restassured.RestAssured;
import io.restassured.specification.RequestSpecification;
import org.assertj.core.api.AbstractAssert;
import org.junit.ClassRule;
import org.junit.Test;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
@@ -34,12 +34,12 @@ import static org.assertj.core.api.Assertions.assertThat;
*
* @author Andy Wilkinson
*/
public class RestAssuredParameterBehaviorTests {
class RestAssuredParameterBehaviorTests {
private static final MediaType APPLICATION_FORM_URLENCODED_ISO_8859_1 = MediaType
.parseMediaType(MediaType.APPLICATION_FORM_URLENCODED_VALUE + ";charset=ISO-8859-1");
@ClassRule
@RegisterExtension
public static TomcatServer tomcat = new TomcatServer();
private final RestAssuredRequestConverter factory = new RestAssuredRequestConverter();
@@ -54,7 +54,7 @@ public class RestAssuredParameterBehaviorTests {
});
@Test
public void queryParameterOnGet() {
void queryParameterOnGet() {
this.spec.queryParam("a", "alpha", "apple")
.queryParam("b", "bravo")
.get("/query-parameter")
@@ -64,7 +64,7 @@ public class RestAssuredParameterBehaviorTests {
}
@Test
public void queryParameterOnHead() {
void queryParameterOnHead() {
this.spec.queryParam("a", "alpha", "apple")
.queryParam("b", "bravo")
.head("/query-parameter")
@@ -74,7 +74,7 @@ public class RestAssuredParameterBehaviorTests {
}
@Test
public void queryParameterOnPost() {
void queryParameterOnPost() {
this.spec.queryParam("a", "alpha", "apple")
.queryParam("b", "bravo")
.post("/query-parameter")
@@ -84,7 +84,7 @@ public class RestAssuredParameterBehaviorTests {
}
@Test
public void queryParameterOnPut() {
void queryParameterOnPut() {
this.spec.queryParam("a", "alpha", "apple")
.queryParam("b", "bravo")
.put("/query-parameter")
@@ -94,7 +94,7 @@ public class RestAssuredParameterBehaviorTests {
}
@Test
public void queryParameterOnPatch() {
void queryParameterOnPatch() {
this.spec.queryParam("a", "alpha", "apple")
.queryParam("b", "bravo")
.patch("/query-parameter")
@@ -104,7 +104,7 @@ public class RestAssuredParameterBehaviorTests {
}
@Test
public void queryParameterOnDelete() {
void queryParameterOnDelete() {
this.spec.queryParam("a", "alpha", "apple")
.queryParam("b", "bravo")
.delete("/query-parameter")
@@ -114,7 +114,7 @@ public class RestAssuredParameterBehaviorTests {
}
@Test
public void queryParameterOnOptions() {
void queryParameterOnOptions() {
this.spec.queryParam("a", "alpha", "apple")
.queryParam("b", "bravo")
.options("/query-parameter")
@@ -124,49 +124,49 @@ public class RestAssuredParameterBehaviorTests {
}
@Test
public void paramOnGet() {
void paramOnGet() {
this.spec.param("a", "alpha", "apple").param("b", "bravo").get("/query-parameter").then().statusCode(200);
assertThatRequest(this.request).hasQueryParametersWithMethod(HttpMethod.GET);
}
@Test
public void paramOnHead() {
void paramOnHead() {
this.spec.param("a", "alpha", "apple").param("b", "bravo").head("/query-parameter").then().statusCode(200);
assertThatRequest(this.request).hasQueryParametersWithMethod(HttpMethod.HEAD);
}
@Test
public void paramOnPost() {
void paramOnPost() {
this.spec.param("a", "alpha", "apple").param("b", "bravo").post("/form-url-encoded").then().statusCode(200);
assertThatRequest(this.request).isFormUrlEncodedWithMethod(HttpMethod.POST);
}
@Test
public void paramOnPut() {
void paramOnPut() {
this.spec.param("a", "alpha", "apple").param("b", "bravo").put("/query-parameter").then().statusCode(200);
assertThatRequest(this.request).hasQueryParametersWithMethod(HttpMethod.PUT);
}
@Test
public void paramOnPatch() {
void paramOnPatch() {
this.spec.param("a", "alpha", "apple").param("b", "bravo").patch("/query-parameter").then().statusCode(200);
assertThatRequest(this.request).hasQueryParametersWithMethod(HttpMethod.PATCH);
}
@Test
public void paramOnDelete() {
void paramOnDelete() {
this.spec.param("a", "alpha", "apple").param("b", "bravo").delete("/query-parameter").then().statusCode(200);
assertThatRequest(this.request).hasQueryParametersWithMethod(HttpMethod.DELETE);
}
@Test
public void paramOnOptions() {
void paramOnOptions() {
this.spec.param("a", "alpha", "apple").param("b", "bravo").options("/query-parameter").then().statusCode(200);
assertThatRequest(this.request).hasQueryParametersWithMethod(HttpMethod.OPTIONS);
}
@Test
public void formParamOnGet() {
void formParamOnGet() {
this.spec.formParam("a", "alpha", "apple")
.formParam("b", "bravo")
.get("/query-parameter")
@@ -176,7 +176,7 @@ public class RestAssuredParameterBehaviorTests {
}
@Test
public void formParamOnHead() {
void formParamOnHead() {
this.spec.formParam("a", "alpha", "apple")
.formParam("b", "bravo")
.head("/form-url-encoded")
@@ -186,7 +186,7 @@ public class RestAssuredParameterBehaviorTests {
}
@Test
public void formParamOnPost() {
void formParamOnPost() {
this.spec.formParam("a", "alpha", "apple")
.formParam("b", "bravo")
.post("/form-url-encoded")
@@ -196,7 +196,7 @@ public class RestAssuredParameterBehaviorTests {
}
@Test
public void formParamOnPut() {
void formParamOnPut() {
this.spec.formParam("a", "alpha", "apple")
.formParam("b", "bravo")
.put("/form-url-encoded")
@@ -206,7 +206,7 @@ public class RestAssuredParameterBehaviorTests {
}
@Test
public void formParamOnPatch() {
void formParamOnPatch() {
this.spec.formParam("a", "alpha", "apple")
.formParam("b", "bravo")
.patch("/form-url-encoded")
@@ -216,7 +216,7 @@ public class RestAssuredParameterBehaviorTests {
}
@Test
public void formParamOnDelete() {
void formParamOnDelete() {
this.spec.formParam("a", "alpha", "apple")
.formParam("b", "bravo")
.delete("/form-url-encoded")
@@ -226,7 +226,7 @@ public class RestAssuredParameterBehaviorTests {
}
@Test
public void formParamOnOptions() {
void formParamOnOptions() {
this.spec.formParam("a", "alpha", "apple")
.formParam("b", "bravo")
.options("/form-url-encoded")

View File

@@ -28,8 +28,8 @@ import java.util.Iterator;
import io.restassured.RestAssured;
import io.restassured.specification.FilterableRequestSpecification;
import io.restassured.specification.RequestSpecification;
import org.junit.ClassRule;
import org.junit.Test;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
@@ -47,15 +47,15 @@ import static org.assertj.core.api.Assertions.entry;
*
* @author Andy Wilkinson
*/
public class RestAssuredRequestConverterTests {
class RestAssuredRequestConverterTests {
@ClassRule
@RegisterExtension
public static TomcatServer tomcat = new TomcatServer();
private final RestAssuredRequestConverter factory = new RestAssuredRequestConverter();
@Test
public void requestUri() {
void requestUri() {
RequestSpecification requestSpec = RestAssured.given().port(tomcat.getPort());
requestSpec.get("/foo/bar");
OperationRequest request = this.factory.convert((FilterableRequestSpecification) requestSpec);
@@ -63,7 +63,7 @@ public class RestAssuredRequestConverterTests {
}
@Test
public void requestMethod() {
void requestMethod() {
RequestSpecification requestSpec = RestAssured.given().port(tomcat.getPort());
requestSpec.head("/foo/bar");
OperationRequest request = this.factory.convert((FilterableRequestSpecification) requestSpec);
@@ -71,7 +71,7 @@ public class RestAssuredRequestConverterTests {
}
@Test
public void queryStringParameters() {
void queryStringParameters() {
RequestSpecification requestSpec = RestAssured.given().port(tomcat.getPort()).queryParam("foo", "bar");
requestSpec.get("/");
OperationRequest request = this.factory.convert((FilterableRequestSpecification) requestSpec);
@@ -79,7 +79,7 @@ public class RestAssuredRequestConverterTests {
}
@Test
public void queryStringFromUrlParameters() {
void queryStringFromUrlParameters() {
RequestSpecification requestSpec = RestAssured.given().port(tomcat.getPort());
requestSpec.get("/?foo=bar&foo=qix");
OperationRequest request = this.factory.convert((FilterableRequestSpecification) requestSpec);
@@ -87,7 +87,7 @@ public class RestAssuredRequestConverterTests {
}
@Test
public void paramOnGetRequestIsMappedToQueryString() {
void paramOnGetRequestIsMappedToQueryString() {
RequestSpecification requestSpec = RestAssured.given().port(tomcat.getPort()).param("foo", "bar");
requestSpec.get("/");
OperationRequest request = this.factory.convert((FilterableRequestSpecification) requestSpec);
@@ -95,7 +95,7 @@ public class RestAssuredRequestConverterTests {
}
@Test
public void headers() {
void headers() {
RequestSpecification requestSpec = RestAssured.given().port(tomcat.getPort()).header("Foo", "bar");
requestSpec.get("/");
OperationRequest request = this.factory.convert((FilterableRequestSpecification) requestSpec);
@@ -104,7 +104,7 @@ public class RestAssuredRequestConverterTests {
}
@Test
public void headersWithCustomAccept() {
void headersWithCustomAccept() {
RequestSpecification requestSpec = RestAssured.given()
.port(tomcat.getPort())
.header("Foo", "bar")
@@ -117,7 +117,7 @@ public class RestAssuredRequestConverterTests {
}
@Test
public void cookies() {
void cookies() {
RequestSpecification requestSpec = RestAssured.given()
.port(tomcat.getPort())
.cookie("cookie1", "cookieVal1")
@@ -138,7 +138,7 @@ public class RestAssuredRequestConverterTests {
}
@Test
public void multipart() {
void multipart() {
RequestSpecification requestSpec = RestAssured.given()
.port(tomcat.getPort())
.multiPart("a", "a.txt", "alpha", null)
@@ -156,14 +156,14 @@ public class RestAssuredRequestConverterTests {
}
@Test
public void byteArrayBody() {
void byteArrayBody() {
RequestSpecification requestSpec = RestAssured.given().body("body".getBytes()).port(tomcat.getPort());
requestSpec.post();
this.factory.convert((FilterableRequestSpecification) requestSpec);
}
@Test
public void stringBody() {
void stringBody() {
RequestSpecification requestSpec = RestAssured.given().body("body").port(tomcat.getPort());
requestSpec.post();
OperationRequest request = this.factory.convert((FilterableRequestSpecification) requestSpec);
@@ -171,7 +171,7 @@ public class RestAssuredRequestConverterTests {
}
@Test
public void objectBody() {
void objectBody() {
RequestSpecification requestSpec = RestAssured.given().body(new ObjectBody("bar")).port(tomcat.getPort());
requestSpec.post();
OperationRequest request = this.factory.convert((FilterableRequestSpecification) requestSpec);
@@ -179,7 +179,7 @@ public class RestAssuredRequestConverterTests {
}
@Test
public void byteArrayInputStreamBody() {
void byteArrayInputStreamBody() {
RequestSpecification requestSpec = RestAssured.given()
.body(new ByteArrayInputStream(new byte[] { 1, 2, 3, 4 }))
.port(tomcat.getPort());
@@ -189,7 +189,7 @@ public class RestAssuredRequestConverterTests {
}
@Test
public void fileBody() {
void fileBody() {
RequestSpecification requestSpec = RestAssured.given()
.body(new File("src/test/resources/body.txt"))
.port(tomcat.getPort());
@@ -199,7 +199,7 @@ public class RestAssuredRequestConverterTests {
}
@Test
public void fileInputStreamBody() throws FileNotFoundException {
void fileInputStreamBody() throws FileNotFoundException {
FileInputStream inputStream = new FileInputStream("src/test/resources/body.txt");
RequestSpecification requestSpec = RestAssured.given().body(inputStream).port(tomcat.getPort());
requestSpec.post();
@@ -209,7 +209,7 @@ public class RestAssuredRequestConverterTests {
}
@Test
public void multipartWithByteArrayInputStreamBody() {
void multipartWithByteArrayInputStreamBody() {
RequestSpecification requestSpec = RestAssured.given()
.port(tomcat.getPort())
.multiPart("foo", "foo.txt", new ByteArrayInputStream("foo".getBytes()));
@@ -219,7 +219,7 @@ public class RestAssuredRequestConverterTests {
}
@Test
public void multipartWithStringBody() {
void multipartWithStringBody() {
RequestSpecification requestSpec = RestAssured.given().port(tomcat.getPort()).multiPart("control", "foo");
requestSpec.post();
OperationRequest request = this.factory.convert((FilterableRequestSpecification) requestSpec);
@@ -227,7 +227,7 @@ public class RestAssuredRequestConverterTests {
}
@Test
public void multipartWithByteArrayBody() {
void multipartWithByteArrayBody() {
RequestSpecification requestSpec = RestAssured.given()
.port(tomcat.getPort())
.multiPart("control", "file", "foo".getBytes());
@@ -237,7 +237,7 @@ public class RestAssuredRequestConverterTests {
}
@Test
public void multipartWithFileBody() {
void multipartWithFileBody() {
RequestSpecification requestSpec = RestAssured.given()
.port(tomcat.getPort())
.multiPart(new File("src/test/resources/body.txt"));
@@ -247,7 +247,7 @@ public class RestAssuredRequestConverterTests {
}
@Test
public void multipartWithFileInputStreamBody() throws FileNotFoundException {
void multipartWithFileInputStreamBody() throws FileNotFoundException {
FileInputStream inputStream = new FileInputStream("src/test/resources/body.txt");
RequestSpecification requestSpec = RestAssured.given()
.port(tomcat.getPort())
@@ -259,7 +259,7 @@ public class RestAssuredRequestConverterTests {
}
@Test
public void multipartWithObjectBody() {
void multipartWithObjectBody() {
RequestSpecification requestSpec = RestAssured.given()
.port(tomcat.getPort())
.multiPart("control", new ObjectBody("bar"));

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2022 the original author or authors.
* Copyright 2014-2025 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,7 +19,7 @@ package org.springframework.restdocs.restassured;
import io.restassured.http.Headers;
import io.restassured.response.Response;
import io.restassured.response.ResponseBody;
import org.junit.Test;
import org.junit.jupiter.api.Test;
import org.springframework.http.HttpStatusCode;
import org.springframework.restdocs.operation.OperationResponse;
@@ -33,12 +33,12 @@ import static org.mockito.Mockito.mock;
*
* @author Andy Wilkinson
*/
public class RestAssuredResponseConverterTests {
class RestAssuredResponseConverterTests {
private final RestAssuredResponseConverter converter = new RestAssuredResponseConverter();
@Test
public void responseWithCustomStatus() {
void responseWithCustomStatus() {
Response response = mock(Response.class);
given(response.getStatusCode()).willReturn(600);
given(response.getHeaders()).willReturn(new Headers());

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2023 the original author or authors.
* Copyright 2014-2025 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,11 +22,13 @@ import java.util.Map;
import io.restassured.filter.FilterContext;
import io.restassured.specification.FilterableRequestSpecification;
import io.restassured.specification.FilterableResponseSpecification;
import org.junit.Rule;
import org.junit.Test;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.ArgumentCaptor;
import org.springframework.restdocs.JUnitRestDocumentation;
import org.springframework.restdocs.RestDocumentationContextProvider;
import org.springframework.restdocs.RestDocumentationExtension;
import org.springframework.restdocs.generate.RestDocumentationGenerator;
import org.springframework.restdocs.operation.preprocess.OperationRequestPreprocessor;
import org.springframework.restdocs.operation.preprocess.OperationResponsePreprocessor;
@@ -45,10 +47,8 @@ import static org.mockito.Mockito.verify;
* @author Andy Wilkinson
* @author Filip Hrisafov
*/
public class RestAssuredRestDocumentationConfigurerTests {
@Rule
public final JUnitRestDocumentation restDocumentation = new JUnitRestDocumentation();
@ExtendWith(RestDocumentationExtension.class)
class RestAssuredRestDocumentationConfigurerTests {
private final FilterableRequestSpecification requestSpec = mock(FilterableRequestSpecification.class);
@@ -56,17 +56,21 @@ public class RestAssuredRestDocumentationConfigurerTests {
private final FilterContext filterContext = mock(FilterContext.class);
private final RestAssuredRestDocumentationConfigurer configurer = new RestAssuredRestDocumentationConfigurer(
this.restDocumentation);
private RestAssuredRestDocumentationConfigurer configurer;
@BeforeEach
void setUp(RestDocumentationContextProvider restDocumentation) {
this.configurer = new RestAssuredRestDocumentationConfigurer(restDocumentation);
}
@Test
public void nextFilterIsCalled() {
void nextFilterIsCalled() {
this.configurer.filter(this.requestSpec, this.responseSpec, this.filterContext);
verify(this.filterContext).next(this.requestSpec, this.responseSpec);
}
@Test
public void configurationIsAddedToTheContext() {
void configurationIsAddedToTheContext() {
this.configurer.operationPreprocessors()
.withRequestDefaults(Preprocessors.prettyPrint())
.withResponseDefaults(Preprocessors.modifyHeaders().remove("Foo"))

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2023 the original author or authors.
* Copyright 2014-2025 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,14 +29,15 @@ import java.util.regex.Pattern;
import io.restassured.builder.RequestSpecBuilder;
import io.restassured.specification.RequestSpecification;
import org.assertj.core.api.Condition;
import org.junit.ClassRule;
import org.junit.Rule;
import org.junit.Test;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.restdocs.JUnitRestDocumentation;
import org.springframework.restdocs.RestDocumentationContextProvider;
import org.springframework.restdocs.RestDocumentationExtension;
import org.springframework.restdocs.templates.TemplateFormat;
import org.springframework.restdocs.templates.TemplateFormats;
import org.springframework.restdocs.testfixtures.SnippetConditions;
@@ -80,18 +81,16 @@ import static org.springframework.restdocs.restassured.RestAssuredRestDocumentat
* @author Tomasz Kopczynski
* @author Filip Hrisafov
*/
public class RestAssuredRestDocumentationIntegrationTests {
@ExtendWith(RestDocumentationExtension.class)
class RestAssuredRestDocumentationIntegrationTests {
@Rule
public JUnitRestDocumentation restDocumentation = new JUnitRestDocumentation();
@ClassRule
public static TomcatServer tomcat = new TomcatServer();
@RegisterExtension
private static TomcatServer tomcat = new TomcatServer();
@Test
public void defaultSnippetGeneration() {
void defaultSnippetGeneration(RestDocumentationContextProvider restDocumentation) {
given().port(tomcat.getPort())
.filter(documentationConfiguration(this.restDocumentation))
.filter(documentationConfiguration(restDocumentation))
.filter(document("default"))
.get("/")
.then()
@@ -101,10 +100,10 @@ public class RestAssuredRestDocumentationIntegrationTests {
}
@Test
public void curlSnippetWithContent() {
void curlSnippetWithContent(RestDocumentationContextProvider restDocumentation) {
String contentType = "text/plain; charset=UTF-8";
given().port(tomcat.getPort())
.filter(documentationConfiguration(this.restDocumentation))
.filter(documentationConfiguration(restDocumentation))
.filter(document("curl-snippet-with-content"))
.accept("application/json")
.body("content")
@@ -120,10 +119,10 @@ public class RestAssuredRestDocumentationIntegrationTests {
}
@Test
public void curlSnippetWithCookies() {
void curlSnippetWithCookies(RestDocumentationContextProvider restDocumentation) {
String contentType = "text/plain; charset=UTF-8";
given().port(tomcat.getPort())
.filter(documentationConfiguration(this.restDocumentation))
.filter(documentationConfiguration(restDocumentation))
.filter(document("curl-snippet-with-cookies"))
.accept("application/json")
.contentType(contentType)
@@ -138,9 +137,9 @@ public class RestAssuredRestDocumentationIntegrationTests {
}
@Test
public void curlSnippetWithEmptyParameterQueryString() {
void curlSnippetWithEmptyParameterQueryString(RestDocumentationContextProvider restDocumentation) {
given().port(tomcat.getPort())
.filter(documentationConfiguration(this.restDocumentation))
.filter(documentationConfiguration(restDocumentation))
.filter(document("curl-snippet-with-empty-parameter-query-string"))
.accept("application/json")
.param("a", "")
@@ -155,9 +154,9 @@ public class RestAssuredRestDocumentationIntegrationTests {
}
@Test
public void curlSnippetWithQueryStringOnPost() {
void curlSnippetWithQueryStringOnPost(RestDocumentationContextProvider restDocumentation) {
given().port(tomcat.getPort())
.filter(documentationConfiguration(this.restDocumentation))
.filter(documentationConfiguration(restDocumentation))
.filter(document("curl-snippet-with-query-string"))
.accept("application/json")
.param("foo", "bar")
@@ -174,9 +173,9 @@ public class RestAssuredRestDocumentationIntegrationTests {
}
@Test
public void linksSnippet() {
void linksSnippet(RestDocumentationContextProvider restDocumentation) {
given().port(tomcat.getPort())
.filter(documentationConfiguration(this.restDocumentation))
.filter(documentationConfiguration(restDocumentation))
.filter(document("links", links(linkWithRel("rel").description("The description"))))
.accept("application/json")
.get("/")
@@ -187,9 +186,9 @@ public class RestAssuredRestDocumentationIntegrationTests {
}
@Test
public void pathParametersSnippet() {
void pathParametersSnippet(RestDocumentationContextProvider restDocumentation) {
given().port(tomcat.getPort())
.filter(documentationConfiguration(this.restDocumentation))
.filter(documentationConfiguration(restDocumentation))
.filter(document("path-parameters",
pathParameters(parameterWithName("foo").description("The description"))))
.accept("application/json")
@@ -201,9 +200,9 @@ public class RestAssuredRestDocumentationIntegrationTests {
}
@Test
public void queryParametersSnippet() {
void queryParametersSnippet(RestDocumentationContextProvider restDocumentation) {
given().port(tomcat.getPort())
.filter(documentationConfiguration(this.restDocumentation))
.filter(documentationConfiguration(restDocumentation))
.filter(document("query-parameters",
queryParameters(parameterWithName("foo").description("The description"))))
.accept("application/json")
@@ -216,9 +215,9 @@ public class RestAssuredRestDocumentationIntegrationTests {
}
@Test
public void requestFieldsSnippet() {
void requestFieldsSnippet(RestDocumentationContextProvider restDocumentation) {
given().port(tomcat.getPort())
.filter(documentationConfiguration(this.restDocumentation))
.filter(documentationConfiguration(restDocumentation))
.filter(document("request-fields", requestFields(fieldWithPath("a").description("The description"))))
.accept("application/json")
.body("{\"a\":\"alpha\"}")
@@ -230,9 +229,9 @@ public class RestAssuredRestDocumentationIntegrationTests {
}
@Test
public void requestPartsSnippet() {
void requestPartsSnippet(RestDocumentationContextProvider restDocumentation) {
given().port(tomcat.getPort())
.filter(documentationConfiguration(this.restDocumentation))
.filter(documentationConfiguration(restDocumentation))
.filter(document("request-parts", requestParts(partWithName("a").description("The description"))))
.multiPart("a", "foo")
.post("/upload")
@@ -243,9 +242,9 @@ public class RestAssuredRestDocumentationIntegrationTests {
}
@Test
public void responseFieldsSnippet() {
void responseFieldsSnippet(RestDocumentationContextProvider restDocumentation) {
given().port(tomcat.getPort())
.filter(documentationConfiguration(this.restDocumentation))
.filter(documentationConfiguration(restDocumentation))
.filter(document("response-fields",
responseFields(fieldWithPath("a").description("The description"),
subsectionWithPath("links").description("Links to other resources"))))
@@ -258,9 +257,9 @@ public class RestAssuredRestDocumentationIntegrationTests {
}
@Test
public void parameterizedOutputDirectory() {
void parameterizedOutputDirectory(RestDocumentationContextProvider restDocumentation) {
given().port(tomcat.getPort())
.filter(documentationConfiguration(this.restDocumentation))
.filter(documentationConfiguration(restDocumentation))
.filter(document("{method-name}"))
.get("/")
.then()
@@ -270,9 +269,9 @@ public class RestAssuredRestDocumentationIntegrationTests {
}
@Test
public void multiStep() {
void multiStep(RestDocumentationContextProvider restDocumentation) {
RequestSpecification spec = new RequestSpecBuilder().setPort(tomcat.getPort())
.addFilter(documentationConfiguration(this.restDocumentation))
.addFilter(documentationConfiguration(restDocumentation))
.addFilter(document("{method-name}-{step}"))
.build();
given(spec).get("/").then().statusCode(200);
@@ -287,10 +286,10 @@ public class RestAssuredRestDocumentationIntegrationTests {
}
@Test
public void additionalSnippets() {
void additionalSnippets(RestDocumentationContextProvider restDocumentation) {
RestDocumentationFilter documentation = document("{method-name}-{step}");
RequestSpecification spec = new RequestSpecBuilder().setPort(tomcat.getPort())
.addFilter(documentationConfiguration(this.restDocumentation))
.addFilter(documentationConfiguration(restDocumentation))
.addFilter(documentation)
.build();
given(spec)
@@ -304,9 +303,9 @@ public class RestAssuredRestDocumentationIntegrationTests {
}
@Test
public void responseWithCookie() {
void responseWithCookie(RestDocumentationContextProvider restDocumentation) {
given().port(tomcat.getPort())
.filter(documentationConfiguration(this.restDocumentation))
.filter(documentationConfiguration(restDocumentation))
.filter(document("set-cookie",
preprocessResponse(modifyHeaders().remove(HttpHeaders.DATE).remove(HttpHeaders.CONTENT_TYPE))))
.get("/set-cookie")
@@ -322,10 +321,10 @@ public class RestAssuredRestDocumentationIntegrationTests {
}
@Test
public void preprocessedRequest() {
void preprocessedRequest(RestDocumentationContextProvider restDocumentation) {
Pattern pattern = Pattern.compile("(\"alpha\")");
given().port(tomcat.getPort())
.filter(documentationConfiguration(this.restDocumentation))
.filter(documentationConfiguration(restDocumentation))
.header("a", "alpha")
.header("b", "bravo")
.contentType("application/json")
@@ -356,10 +355,10 @@ public class RestAssuredRestDocumentationIntegrationTests {
}
@Test
public void defaultPreprocessedRequest() {
void defaultPreprocessedRequest(RestDocumentationContextProvider restDocumentation) {
Pattern pattern = Pattern.compile("(\"alpha\")");
given().port(tomcat.getPort())
.filter(documentationConfiguration(this.restDocumentation).operationPreprocessors()
.filter(documentationConfiguration(restDocumentation).operationPreprocessors()
.withRequestDefaults(prettyPrint(), replacePattern(pattern, "\"<<beta>>\""), modifyUris().removePort(),
modifyHeaders().remove("a").remove(HttpHeaders.CONTENT_LENGTH)))
.header("a", "alpha")
@@ -381,10 +380,10 @@ public class RestAssuredRestDocumentationIntegrationTests {
}
@Test
public void preprocessedResponse() {
void preprocessedResponse(RestDocumentationContextProvider restDocumentation) {
Pattern pattern = Pattern.compile("(\"alpha\")");
given().port(tomcat.getPort())
.filter(documentationConfiguration(this.restDocumentation))
.filter(documentationConfiguration(restDocumentation))
.filter(document("original-response"))
.filter(document("preprocessed-response",
preprocessResponse(prettyPrint(), maskLinks(),
@@ -407,10 +406,10 @@ public class RestAssuredRestDocumentationIntegrationTests {
}
@Test
public void defaultPreprocessedResponse() {
void defaultPreprocessedResponse(RestDocumentationContextProvider restDocumentation) {
Pattern pattern = Pattern.compile("(\"alpha\")");
given().port(tomcat.getPort())
.filter(documentationConfiguration(this.restDocumentation).operationPreprocessors()
.filter(documentationConfiguration(restDocumentation).operationPreprocessors()
.withResponseDefaults(prettyPrint(), maskLinks(),
modifyHeaders().remove("a").remove("Transfer-Encoding").remove("Date").remove("Server"),
replacePattern(pattern, "\"<<beta>>\""),
@@ -432,7 +431,7 @@ public class RestAssuredRestDocumentationIntegrationTests {
}
@Test
public void customSnippetTemplate() throws MalformedURLException {
void customSnippetTemplate(RestDocumentationContextProvider restDocumentation) throws MalformedURLException {
ClassLoader classLoader = new URLClassLoader(
new URL[] { new File("src/test/resources/custom-snippet-templates").toURI().toURL() },
getClass().getClassLoader());
@@ -441,7 +440,7 @@ public class RestAssuredRestDocumentationIntegrationTests {
try {
given().port(tomcat.getPort())
.accept("application/json")
.filter(documentationConfiguration(this.restDocumentation))
.filter(documentationConfiguration(restDocumentation))
.filter(document("custom-snippet-template"))
.get("/")
.then()
@@ -455,7 +454,7 @@ public class RestAssuredRestDocumentationIntegrationTests {
}
@Test
public void exceptionShouldBeThrownWhenCallDocumentRequestSpecificationNotConfigured() {
void exceptionShouldBeThrownWhenCallDocumentRequestSpecificationNotConfigured() {
assertThatThrownBy(() -> given().port(tomcat.getPort()).filter(document("default")).get("/"))
.isInstanceOf(IllegalStateException.class)
.hasMessage("REST Docs configuration not found. Did you forget to add a "
@@ -463,7 +462,7 @@ public class RestAssuredRestDocumentationIntegrationTests {
}
@Test
public void exceptionShouldBeThrownWhenCallDocumentSnippetsRequestSpecificationNotConfigured() {
void exceptionShouldBeThrownWhenCallDocumentSnippetsRequestSpecificationNotConfigured() {
RestDocumentationFilter documentation = document("{method-name}-{step}");
assertThatThrownBy(() -> given().port(tomcat.getPort())
.filter(documentation.document(responseHeaders(headerWithName("a").description("one"))))

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2023 the original author or authors.
* Copyright 2014-2025 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.
@@ -32,46 +32,57 @@ import jakarta.servlet.http.HttpServletResponse;
import org.apache.catalina.Context;
import org.apache.catalina.LifecycleException;
import org.apache.catalina.startup.Tomcat;
import org.junit.rules.ExternalResource;
import org.junit.jupiter.api.extension.AfterAllCallback;
import org.junit.jupiter.api.extension.BeforeAllCallback;
import org.junit.jupiter.api.extension.Extension;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.jupiter.api.extension.ExtensionContext.Namespace;
import org.junit.jupiter.api.extension.ExtensionContext.Store;
import org.springframework.http.MediaType;
import org.springframework.util.FileCopyUtils;
/**
* {@link ExternalResource} that starts and stops a Tomcat server.
* {@link Extension} that starts and stops a Tomcat server.
*
* @author Andy Wilkinson
*/
class TomcatServer extends ExternalResource {
private Tomcat tomcat;
class TomcatServer implements BeforeAllCallback, AfterAllCallback {
private int port;
@Override
protected void before() throws LifecycleException {
this.tomcat = new Tomcat();
this.tomcat.getConnector().setPort(0);
Context context = this.tomcat.addContext("/", null);
this.tomcat.addServlet("/", "test", new TestServlet());
context.addServletMappingDecoded("/", "test");
this.tomcat.addServlet("/", "set-cookie", new CookiesServlet());
context.addServletMappingDecoded("/set-cookie", "set-cookie");
this.tomcat.addServlet("/", "query-parameter", new QueryParameterServlet());
context.addServletMappingDecoded("/query-parameter", "query-parameter");
this.tomcat.addServlet("/", "form-url-encoded", new FormUrlEncodedServlet());
context.addServletMappingDecoded("/form-url-encoded", "form-url-encoded");
this.tomcat.start();
this.port = this.tomcat.getConnector().getLocalPort();
public void beforeAll(ExtensionContext extensionContext) {
Store store = extensionContext.getStore(Namespace.create(TomcatServer.class));
store.getOrComputeIfAbsent(Tomcat.class, (key) -> {
Tomcat tomcat = new Tomcat();
tomcat.getConnector().setPort(0);
Context context = tomcat.addContext("/", null);
tomcat.addServlet("/", "test", new TestServlet());
context.addServletMappingDecoded("/", "test");
tomcat.addServlet("/", "set-cookie", new CookiesServlet());
context.addServletMappingDecoded("/set-cookie", "set-cookie");
tomcat.addServlet("/", "query-parameter", new QueryParameterServlet());
context.addServletMappingDecoded("/query-parameter", "query-parameter");
tomcat.addServlet("/", "form-url-encoded", new FormUrlEncodedServlet());
context.addServletMappingDecoded("/form-url-encoded", "form-url-encoded");
try {
tomcat.start();
}
catch (Exception ex) {
throw new RuntimeException(ex);
}
this.port = tomcat.getConnector().getLocalPort();
return tomcat;
});
}
@Override
protected void after() {
try {
this.tomcat.stop();
}
catch (LifecycleException ex) {
throw new RuntimeException(ex);
public void afterAll(ExtensionContext extensionContext) throws LifecycleException {
Store store = extensionContext.getStore(Namespace.create(TomcatServer.class));
Tomcat tomcat = store.get(Tomcat.class, Tomcat.class);
if (tomcat != null) {
tomcat.stop();
}
}

Some files were not shown because too many files have changed in this diff Show More