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"))) internal(platform(project(":spring-restdocs-platform")))
testImplementation("junit:junit")
testImplementation("org.apache.pdfbox:pdfbox") testImplementation("org.apache.pdfbox:pdfbox")
testImplementation("org.assertj:assertj-core") testImplementation("org.assertj:assertj-core")
testImplementation("org.junit.jupiter:junit-jupiter")
testImplementation("org.springframework:spring-core") testImplementation("org.springframework:spring-core")
testRuntimeOnly("org.asciidoctor:asciidoctorj-pdf") 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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with 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.Attributes;
import org.asciidoctor.Options; import org.asciidoctor.Options;
import org.asciidoctor.SafeMode; import org.asciidoctor.SafeMode;
import org.junit.After; import org.junit.jupiter.api.AfterEach;
import org.junit.Before; import org.junit.jupiter.api.BeforeEach;
import org.junit.Rule; import org.junit.jupiter.api.Test;
import org.junit.Test; import org.junit.jupiter.api.io.TempDir;
import org.junit.rules.TemporaryFolder;
import org.springframework.util.FileSystemUtils; import org.springframework.util.FileSystemUtils;
@@ -51,42 +50,42 @@ import static org.assertj.core.api.Assertions.assertThat;
* @author Gerrit Meier * @author Gerrit Meier
* @author Andy Wilkinson * @author Andy Wilkinson
*/ */
public abstract class AbstractOperationBlockMacroTests { abstract class AbstractOperationBlockMacroTests {
@Rule
public TemporaryFolder temp = new TemporaryFolder();
private Options options;
private final Asciidoctor asciidoctor = Asciidoctor.Factory.create(); private final Asciidoctor asciidoctor = Asciidoctor.Factory.create();
@Before @TempDir
public void setUp() throws IOException { protected File temp;
private Options options;
@BeforeEach
void setUp() throws IOException {
prepareOperationSnippets(getBuildOutputLocation()); prepareOperationSnippets(getBuildOutputLocation());
this.options = Options.builder().safe(SafeMode.UNSAFE).baseDir(getSourceLocation()).build(); this.options = Options.builder().safe(SafeMode.UNSAFE).baseDir(getSourceLocation()).build();
this.options.setAttributes(getAttributes()); this.options.setAttributes(getAttributes());
CapturingLogHandler.clear(); CapturingLogHandler.clear();
} }
@After @AfterEach
public void verifyLogging() { void verifyLogging() {
assertThat(CapturingLogHandler.getLogRecords()).isEmpty(); 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"); File destination = new File(buildOutputLocation, "generated-snippets/some-operation");
destination.mkdirs(); destination.mkdirs();
FileSystemUtils.copyRecursively(new File("src/test/resources/some-operation"), destination); FileSystemUtils.copyRecursively(new File("src/test/resources/some-operation"), destination);
} }
@Test @Test
public void codeBlockSnippetInclude() throws Exception { void codeBlockSnippetInclude() throws Exception {
String result = this.asciidoctor.convert("operation::some-operation[snippets='curl-request']", this.options); String result = this.asciidoctor.convert("operation::some-operation[snippets='curl-request']", this.options);
assertThat(result).isEqualTo(getExpectedContentFromFile("snippet-simple")); assertThat(result).isEqualTo(getExpectedContentFromFile("snippet-simple"));
} }
@Test @Test
public void operationWithParameterizedName() throws Exception { void operationWithParameterizedName() throws Exception {
Attributes attributes = getAttributes(); Attributes attributes = getAttributes();
attributes.setAttribute("name", "some"); attributes.setAttribute("name", "some");
this.options.setAttributes(attributes); this.options.setAttributes(attributes);
@@ -95,20 +94,20 @@ public abstract class AbstractOperationBlockMacroTests {
} }
@Test @Test
public void codeBlockSnippetIncludeWithPdfBackend() throws Exception { void codeBlockSnippetIncludeWithPdfBackend() throws Exception {
File output = configurePdfOutput(); File output = configurePdfOutput();
this.asciidoctor.convert("operation::some-operation[snippets='curl-request']", this.options); this.asciidoctor.convert("operation::some-operation[snippets='curl-request']", this.options);
assertThat(extractStrings(output)).containsExactly("Curl request", "$ curl 'http://localhost:8080/' -i", "1"); assertThat(extractStrings(output)).containsExactly("Curl request", "$ curl 'http://localhost:8080/' -i", "1");
} }
@Test @Test
public void tableSnippetInclude() throws Exception { void tableSnippetInclude() throws Exception {
String result = this.asciidoctor.convert("operation::some-operation[snippets='response-fields']", this.options); String result = this.asciidoctor.convert("operation::some-operation[snippets='response-fields']", this.options);
assertThat(result).isEqualTo(getExpectedContentFromFile("snippet-table")); assertThat(result).isEqualTo(getExpectedContentFromFile("snippet-table"));
} }
@Test @Test
public void tableSnippetIncludeWithPdfBackend() throws Exception { void tableSnippetIncludeWithPdfBackend() throws Exception {
File output = configurePdfOutput(); File output = configurePdfOutput();
this.asciidoctor.convert("operation::some-operation[snippets='response-fields']", this.options); this.asciidoctor.convert("operation::some-operation[snippets='response-fields']", this.options);
assertThat(extractStrings(output)).containsExactly("Response fields", "Path", "Type", "Description", "a", assertThat(extractStrings(output)).containsExactly("Response fields", "Path", "Type", "Description", "a",
@@ -116,14 +115,14 @@ public abstract class AbstractOperationBlockMacroTests {
} }
@Test @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" 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); + "operation::some-operation[snippets='curl-request']\n\n== C\n", this.options);
assertThat(result).isEqualTo(getExpectedContentFromFile("snippet-in-section")); assertThat(result).isEqualTo(getExpectedContentFromFile("snippet-in-section"));
} }
@Test @Test
public void includeSnippetInSectionWithAbsoluteLevelOffset() throws Exception { void includeSnippetInSectionWithAbsoluteLevelOffset() throws Exception {
String result = this.asciidoctor String result = this.asciidoctor
.convert("= A\n:doctype: book\n:sectnums:\n:leveloffset: 1\n\nAlpha\n\n= B\n\nBravo\n\n" .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); + "operation::some-operation[snippets='curl-request']\n\n= C\n", this.options);
@@ -131,7 +130,7 @@ public abstract class AbstractOperationBlockMacroTests {
} }
@Test @Test
public void includeSnippetInSectionWithRelativeLevelOffset() throws Exception { void includeSnippetInSectionWithRelativeLevelOffset() throws Exception {
String result = this.asciidoctor String result = this.asciidoctor
.convert("= A\n:doctype: book\n:sectnums:\n:leveloffset: +1\n\nAlpha\n\n= B\n\nBravo\n\n" .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); + "operation::some-operation[snippets='curl-request']\n\n= C\n", this.options);
@@ -139,7 +138,7 @@ public abstract class AbstractOperationBlockMacroTests {
} }
@Test @Test
public void includeSnippetInSectionWithPdfBackend() throws Exception { void includeSnippetInSectionWithPdfBackend() throws Exception {
File output = configurePdfOutput(); File output = configurePdfOutput();
this.asciidoctor.convert("== Section\n" + "operation::some-operation[snippets='curl-request']", this.options); this.asciidoctor.convert("== Section\n" + "operation::some-operation[snippets='curl-request']", this.options);
assertThat(extractStrings(output)).containsExactly("Section", "Curl request", assertThat(extractStrings(output)).containsExactly("Section", "Curl request",
@@ -147,26 +146,26 @@ public abstract class AbstractOperationBlockMacroTests {
} }
@Test @Test
public void includeMultipleSnippets() throws Exception { void includeMultipleSnippets() throws Exception {
String result = this.asciidoctor.convert("operation::some-operation[snippets='curl-request,http-request']", String result = this.asciidoctor.convert("operation::some-operation[snippets='curl-request,http-request']",
this.options); this.options);
assertThat(result).isEqualTo(getExpectedContentFromFile("multiple-snippets")); assertThat(result).isEqualTo(getExpectedContentFromFile("multiple-snippets"));
} }
@Test @Test
public void useMacroWithoutSnippetAttributeAddsAllSnippets() throws Exception { void useMacroWithoutSnippetAttributeAddsAllSnippets() throws Exception {
String result = this.asciidoctor.convert("operation::some-operation[]", this.options); String result = this.asciidoctor.convert("operation::some-operation[]", this.options);
assertThat(result).isEqualTo(getExpectedContentFromFile("all-snippets")); assertThat(result).isEqualTo(getExpectedContentFromFile("all-snippets"));
} }
@Test @Test
public void useMacroWithEmptySnippetAttributeAddsAllSnippets() throws Exception { void useMacroWithEmptySnippetAttributeAddsAllSnippets() throws Exception {
String result = this.asciidoctor.convert("operation::some-operation[snippets=]", this.options); String result = this.asciidoctor.convert("operation::some-operation[snippets=]", this.options);
assertThat(result).isEqualTo(getExpectedContentFromFile("all-snippets")); assertThat(result).isEqualTo(getExpectedContentFromFile("all-snippets"));
} }
@Test @Test
public void includingMissingSnippetAddsWarning() throws Exception { void includingMissingSnippetAddsWarning() throws Exception {
String result = this.asciidoctor.convert("operation::some-operation[snippets='missing-snippet']", this.options); String result = this.asciidoctor.convert("operation::some-operation[snippets='missing-snippet']", this.options);
assertThat(result).startsWith(getExpectedContentFromFile("missing-snippet")); assertThat(result).startsWith(getExpectedContentFromFile("missing-snippet"));
assertThat(CapturingLogHandler.getLogRecords()).hasSize(1); assertThat(CapturingLogHandler.getLogRecords()).hasSize(1);
@@ -177,13 +176,13 @@ public abstract class AbstractOperationBlockMacroTests {
} }
@Test @Test
public void defaultTitleIsProvidedForCustomSnippet() throws Exception { void defaultTitleIsProvidedForCustomSnippet() throws Exception {
String result = this.asciidoctor.convert("operation::some-operation[snippets='custom-snippet']", this.options); String result = this.asciidoctor.convert("operation::some-operation[snippets='custom-snippet']", this.options);
assertThat(result).isEqualTo(getExpectedContentFromFile("custom-snippet-default-title")); assertThat(result).isEqualTo(getExpectedContentFromFile("custom-snippet-default-title"));
} }
@Test @Test
public void missingOperationIsHandledGracefully() throws Exception { void missingOperationIsHandledGracefully() throws Exception {
String result = this.asciidoctor.convert("operation::missing-operation[]", this.options); String result = this.asciidoctor.convert("operation::missing-operation[]", this.options);
assertThat(result).startsWith(getExpectedContentFromFile("missing-operation")); assertThat(result).startsWith(getExpectedContentFromFile("missing-operation"));
assertThat(CapturingLogHandler.getLogRecords()).hasSize(1); assertThat(CapturingLogHandler.getLogRecords()).hasSize(1);
@@ -194,14 +193,14 @@ public abstract class AbstractOperationBlockMacroTests {
} }
@Test @Test
public void titleOfBuiltInSnippetCanBeCustomizedUsingDocumentAttribute() throws URISyntaxException, IOException { void titleOfBuiltInSnippetCanBeCustomizedUsingDocumentAttribute() throws URISyntaxException, IOException {
String result = this.asciidoctor.convert(":operation-curl-request-title: Example request\n" String result = this.asciidoctor.convert(":operation-curl-request-title: Example request\n"
+ "operation::some-operation[snippets='curl-request']", this.options); + "operation::some-operation[snippets='curl-request']", this.options);
assertThat(result).isEqualTo(getExpectedContentFromFile("built-in-snippet-custom-title")); assertThat(result).isEqualTo(getExpectedContentFromFile("built-in-snippet-custom-title"));
} }
@Test @Test
public void titleOfCustomSnippetCanBeCustomizedUsingDocumentAttribute() throws Exception { void titleOfCustomSnippetCanBeCustomizedUsingDocumentAttribute() throws Exception {
String result = this.asciidoctor.convert(":operation-custom-snippet-title: Customized title\n" String result = this.asciidoctor.convert(":operation-custom-snippet-title: Customized title\n"
+ "operation::some-operation[snippets='custom-snippet']", this.options); + "operation::some-operation[snippets='custom-snippet']", this.options);
assertThat(result).isEqualTo(getExpectedContentFromFile("custom-snippet-custom-title")); 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.Asciidoctor;
import org.asciidoctor.Attributes; import org.asciidoctor.Attributes;
import org.asciidoctor.Options; import org.asciidoctor.Options;
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.assertThat;
@@ -30,23 +30,23 @@ import static org.assertj.core.api.Assertions.assertThat;
* *
* @author Andy Wilkinson * @author Andy Wilkinson
*/ */
public class DefaultAttributesPreprocessorTests { class DefaultAttributesPreprocessorTests {
@Test @Test
public void snippetsAttributeIsSet() { void snippetsAttributeIsSet() {
String converted = createAsciidoctor().convert("{snippets}", createOptions("projectdir=../../..")); String converted = createAsciidoctor().convert("{snippets}", createOptions("projectdir=../../.."));
assertThat(converted).contains("build" + File.separatorChar + "generated-snippets"); assertThat(converted).contains("build" + File.separatorChar + "generated-snippets");
} }
@Test @Test
public void snippetsAttributeFromConvertArgumentIsNotOverridden() { void snippetsAttributeFromConvertArgumentIsNotOverridden() {
String converted = createAsciidoctor().convert("{snippets}", String converted = createAsciidoctor().convert("{snippets}",
createOptions("snippets=custom projectdir=../../..")); createOptions("snippets=custom projectdir=../../.."));
assertThat(converted).contains("custom"); assertThat(converted).contains("custom");
} }
@Test @Test
public void snippetsAttributeFromDocumentPreambleIsNotOverridden() { void snippetsAttributeFromDocumentPreambleIsNotOverridden() {
String converted = createAsciidoctor().convert(":snippets: custom\n{snippets}", String converted = createAsciidoctor().convert(":snippets: custom\n{snippets}",
createOptions("projectdir=../../..")); createOptions("projectdir=../../.."));
assertThat(converted).contains("custom"); 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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with 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 java.io.File;
import org.asciidoctor.Attributes; import org.asciidoctor.Attributes;
import org.junit.runner.RunWith; import org.junit.jupiter.params.ParameterizedClass;
import org.junit.runners.Parameterized; import org.junit.jupiter.params.provider.ValueSource;
import org.junit.runners.Parameterized.Parameters;
/** /**
* Tests for Ruby operation block macro when used in a Gradle build. * Tests for Ruby operation block macro when used in a Gradle build.
* *
* @author Andy Wilkinson * @author Andy Wilkinson
*/ */
@RunWith(Parameterized.class) @ParameterizedClass
public class GradleOperationBlockMacroTests extends AbstractOperationBlockMacroTests { @ValueSource(strings = { "projectdir", "gradle-projectdir" })
class GradleOperationBlockMacroTests extends AbstractOperationBlockMacroTests {
private final String attributeName; private final String attributeName;
public GradleOperationBlockMacroTests(String attributeName) { GradleOperationBlockMacroTests(String attributeName) {
this.attributeName = attributeName; this.attributeName = attributeName;
} }
@Parameters(name = "{0}")
public static Object[] parameters() {
return new Object[] { "projectdir", "gradle-projectdir" };
}
@Override @Override
protected Attributes getAttributes() { protected Attributes getAttributes() {
Attributes attributes = Attributes.builder() 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(); .build();
return attributes; return attributes;
} }
@Override @Override
protected File getBuildOutputLocation() { protected File getBuildOutputLocation() {
File outputLocation = new File(this.temp.getRoot(), "gradle-project/build"); File outputLocation = new File(this.temp, "gradle-project/build");
outputLocation.mkdirs(); outputLocation.mkdirs();
return outputLocation; return outputLocation;
} }
@Override @Override
protected File getSourceLocation() { 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()) { if (!sourceLocation.exists()) {
sourceLocation.mkdirs(); 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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with 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 java.io.IOException;
import org.asciidoctor.Attributes; import org.asciidoctor.Attributes;
import org.junit.After; import org.junit.jupiter.api.AfterEach;
import org.junit.Before; import org.junit.jupiter.api.BeforeEach;
/** /**
* Tests for Ruby operation block macro when used in a Maven build. * Tests for Ruby operation block macro when used in a Maven build.
* *
* @author Andy Wilkinson * @author Andy Wilkinson
*/ */
public class MavenOperationBlockMacroTests extends AbstractOperationBlockMacroTests { class MavenOperationBlockMacroTests extends AbstractOperationBlockMacroTests {
@Before @BeforeEach
public void setMavenHome() { void setMavenHome() {
System.setProperty("maven.home", "maven-home"); System.setProperty("maven.home", "maven-home");
} }
@After @AfterEach
public void clearMavenHome() { void clearMavenHome() {
System.clearProperty("maven.home"); System.clearProperty("maven.home");
} }
@@ -55,14 +55,14 @@ public class MavenOperationBlockMacroTests extends AbstractOperationBlockMacroTe
@Override @Override
protected File getBuildOutputLocation() { protected File getBuildOutputLocation() {
File outputLocation = new File(this.temp.getRoot(), "maven-project/target"); File outputLocation = new File(this.temp, "maven-project/target");
outputLocation.mkdirs(); outputLocation.mkdirs();
return outputLocation; return outputLocation;
} }
@Override @Override
protected File getSourceLocation() { 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()) { if (!sourceLocation.exists()) {
sourceLocation.mkdirs(); 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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with 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.HashMap;
import java.util.Map; import java.util.Map;
import org.junit.Rule; import org.junit.jupiter.api.Test;
import org.junit.Test; import org.junit.jupiter.api.io.TempDir;
import org.junit.rules.TemporaryFolder;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalStateException; import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
@@ -35,23 +34,23 @@ import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
*/ */
public class SnippetsDirectoryResolverTests { public class SnippetsDirectoryResolverTests {
@Rule @TempDir
public TemporaryFolder temporaryFolder = new TemporaryFolder(); File temp;
@Test @Test
public void mavenProjectsUseTargetGeneratedSnippets() throws IOException { public void mavenProjectsUseTargetGeneratedSnippets() throws IOException {
this.temporaryFolder.newFile("pom.xml"); new File(this.temp, "pom.xml").createNewFile();
Map<String, Object> attributes = new HashMap<>(); 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); File snippetsDirectory = getMavenSnippetsDirectory(attributes);
assertThat(snippetsDirectory).isAbsolute(); 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 @Test
public void illegalStateExceptionWhenMavenPomCannotBeFound() { public void illegalStateExceptionWhenMavenPomCannotBeFound() {
Map<String, Object> attributes = new HashMap<>(); 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); attributes.put("docdir", docdir);
assertThatIllegalStateException().isThrownBy(() -> getMavenSnippetsDirectory(attributes)) assertThatIllegalStateException().isThrownBy(() -> getMavenSnippetsDirectory(attributes))
.withMessage("pom.xml not found in '" + docdir + "' or above"); .withMessage("pom.xml not found in '" + docdir + "' or above");

View File

@@ -32,6 +32,8 @@ task jmustacheRepackJar(type: Jar) { repackJar ->
} }
dependencies { dependencies {
compileOnly("org.apiguardian:apiguardian-api")
implementation("com.fasterxml.jackson.core:jackson-databind") implementation("com.fasterxml.jackson.core:jackson-databind")
implementation("org.springframework:spring-web") implementation("org.springframework:spring-web")
implementation(files(jmustacheRepackJar)) implementation(files(jmustacheRepackJar))
@@ -50,21 +52,29 @@ dependencies {
optional("org.junit.jupiter:junit-jupiter-api") optional("org.junit.jupiter:junit-jupiter-api")
testFixturesApi(platform(project(":spring-restdocs-platform"))) testFixturesApi(platform(project(":spring-restdocs-platform")))
testFixturesApi("junit:junit")
testFixturesApi("org.assertj:assertj-core") 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(files(jmustacheRepackJar))
testFixturesImplementation("org.hamcrest:hamcrest-library") testFixturesImplementation("org.hamcrest:hamcrest-library")
testFixturesImplementation("org.mockito:mockito-core")
testFixturesImplementation("org.springframework:spring-core") testFixturesImplementation("org.springframework:spring-core")
testFixturesImplementation("org.springframework:spring-web") 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.assertj:assertj-core")
testImplementation("org.javamoney:moneta") testImplementation("org.javamoney:moneta")
testImplementation("org.mockito:mockito-core") testImplementation("org.mockito:mockito-core")
testImplementation("org.springframework:spring-test") testImplementation("org.springframework:spring-test")
testRuntimeOnly("org.apache.tomcat.embed:tomcat-embed-el") testRuntimeOnly("org.apache.tomcat.embed:tomcat-embed-el")
testRuntimeOnly("org.junit.platform:junit-platform-engine")
} }
jar { jar {
@@ -81,3 +91,7 @@ components.java.withVariantsFromConfiguration(configurations.testFixturesApiElem
components.java.withVariantsFromConfiguration(configurations.testFixturesRuntimeElements) { components.java.withVariantsFromConfiguration(configurations.testFixturesRuntimeElements) {
skip() 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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with 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() RestDocumentationContext context = (RestDocumentationContext) operation.getAttributes()
.get(RestDocumentationContext.class.getName()); .get(RestDocumentationContext.class.getName());
WriterResolver writerResolver = (WriterResolver) operation.getAttributes().get(WriterResolver.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)) { 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() TemplateEngine templateEngine = (TemplateEngine) operation.getAttributes()
.get(TemplateEngine.class.getName()); .get(TemplateEngine.class.getName());
writer.append(templateEngine.compileTemplate(this.templateName).render(model)); 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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -16,25 +16,16 @@
package org.springframework.restdocs; 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.core.io.FileSystemResource;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.restdocs.templates.TemplateFormat; import org.springframework.restdocs.templates.TemplateFormat;
import org.springframework.restdocs.templates.TemplateFormats; 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;
import org.springframework.restdocs.testfixtures.SnippetConditions.CodeBlockCondition; import org.springframework.restdocs.testfixtures.SnippetConditions.CodeBlockCondition;
import org.springframework.restdocs.testfixtures.SnippetConditions.HttpRequestCondition; import org.springframework.restdocs.testfixtures.SnippetConditions.HttpRequestCondition;
import org.springframework.restdocs.testfixtures.SnippetConditions.HttpResponseCondition; import org.springframework.restdocs.testfixtures.SnippetConditions.HttpResponseCondition;
import org.springframework.restdocs.testfixtures.SnippetConditions.TableCondition; import org.springframework.restdocs.testfixtures.SnippetConditions.TableCondition;
import org.springframework.restdocs.testfixtures.jupiter.AssertableSnippets;
import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestMethod;
/** /**
@@ -42,28 +33,11 @@ import org.springframework.web.bind.annotation.RequestMethod;
* *
* @author Andy Wilkinson * @author Andy Wilkinson
*/ */
@RunWith(Parameterized.class)
public abstract class AbstractSnippetTests { public abstract class AbstractSnippetTests {
protected final TemplateFormat templateFormat; protected final TemplateFormat templateFormat = TemplateFormats.asciidoctor();
@Rule protected AssertableSnippets snippets;
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);
}
public CodeBlockCondition<?> codeBlock(String language) { public CodeBlockCondition<?> codeBlock(String language) {
return this.codeBlock(language, null); 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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with 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.HashMap;
import java.util.Map; import java.util.Map;
import org.junit.Test; import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor; import org.mockito.ArgumentCaptor;
import org.mockito.InOrder; import org.mockito.InOrder;
import org.mockito.Mockito; import org.mockito.Mockito;
@@ -55,7 +55,7 @@ import static org.mockito.Mockito.verifyNoMoreInteractions;
* @author Andy Wilkinson * @author Andy Wilkinson
* @author Filip Hrisafov * @author Filip Hrisafov
*/ */
public class RestDocumentationGeneratorTests { class RestDocumentationGeneratorTests {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
private final RequestConverter<Object> requestConverter = mock(RequestConverter.class); private final RequestConverter<Object> requestConverter = mock(RequestConverter.class);
@@ -80,7 +80,7 @@ public class RestDocumentationGeneratorTests {
private final OperationPreprocessor responsePreprocessor = mock(OperationPreprocessor.class); private final OperationPreprocessor responsePreprocessor = mock(OperationPreprocessor.class);
@Test @Test
public void basicHandling() throws IOException { void basicHandling() throws IOException {
given(this.requestConverter.convert(this.request)).willReturn(this.operationRequest); given(this.requestConverter.convert(this.request)).willReturn(this.operationRequest);
given(this.responseConverter.convert(this.response)).willReturn(this.operationResponse); given(this.responseConverter.convert(this.response)).willReturn(this.operationResponse);
HashMap<String, Object> configuration = new HashMap<>(); HashMap<String, Object> configuration = new HashMap<>();
@@ -90,7 +90,7 @@ public class RestDocumentationGeneratorTests {
} }
@Test @Test
public void defaultSnippetsAreCalled() throws IOException { void defaultSnippetsAreCalled() throws IOException {
given(this.requestConverter.convert(this.request)).willReturn(this.operationRequest); given(this.requestConverter.convert(this.request)).willReturn(this.operationRequest);
given(this.responseConverter.convert(this.response)).willReturn(this.operationResponse); given(this.responseConverter.convert(this.response)).willReturn(this.operationResponse);
HashMap<String, Object> configuration = new HashMap<>(); HashMap<String, Object> configuration = new HashMap<>();
@@ -107,7 +107,7 @@ public class RestDocumentationGeneratorTests {
} }
@Test @Test
public void defaultOperationRequestPreprocessorsAreCalled() throws IOException { void defaultOperationRequestPreprocessorsAreCalled() throws IOException {
given(this.requestConverter.convert(this.request)).willReturn(this.operationRequest); given(this.requestConverter.convert(this.request)).willReturn(this.operationRequest);
given(this.responseConverter.convert(this.response)).willReturn(this.operationResponse); given(this.responseConverter.convert(this.response)).willReturn(this.operationResponse);
HashMap<String, Object> configuration = new HashMap<>(); HashMap<String, Object> configuration = new HashMap<>();
@@ -128,7 +128,7 @@ public class RestDocumentationGeneratorTests {
} }
@Test @Test
public void defaultOperationResponsePreprocessorsAreCalled() throws IOException { void defaultOperationResponsePreprocessorsAreCalled() throws IOException {
given(this.requestConverter.convert(this.request)).willReturn(this.operationRequest); given(this.requestConverter.convert(this.request)).willReturn(this.operationRequest);
given(this.responseConverter.convert(this.response)).willReturn(this.operationResponse); given(this.responseConverter.convert(this.response)).willReturn(this.operationResponse);
HashMap<String, Object> configuration = new HashMap<>(); HashMap<String, Object> configuration = new HashMap<>();
@@ -149,7 +149,7 @@ public class RestDocumentationGeneratorTests {
} }
@Test @Test
public void newGeneratorOnlyCallsItsSnippets() throws IOException { void newGeneratorOnlyCallsItsSnippets() throws IOException {
OperationRequestPreprocessor requestPreprocessor = mock(OperationRequestPreprocessor.class); OperationRequestPreprocessor requestPreprocessor = mock(OperationRequestPreprocessor.class);
OperationResponsePreprocessor responsePreprocessor = mock(OperationResponsePreprocessor.class); OperationResponsePreprocessor responsePreprocessor = mock(OperationResponsePreprocessor.class);
given(this.requestConverter.convert(this.request)).willReturn(this.operationRequest); 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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with 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.Arrays;
import java.util.Collections; 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.assertj.core.api.Assertions.assertThat;
@@ -29,27 +29,27 @@ import static org.assertj.core.api.Assertions.assertThat;
* @author Tomasz Kopczynski * @author Tomasz Kopczynski
* @author Andy Wilkinson * @author Andy Wilkinson
*/ */
public class ConcatenatingCommandFormatterTests { class ConcatenatingCommandFormatterTests {
private CommandFormatter singleLineFormat = new ConcatenatingCommandFormatter(" "); private CommandFormatter singleLineFormat = new ConcatenatingCommandFormatter(" ");
@Test @Test
public void formattingAnEmptyListProducesAnEmptyString() { void formattingAnEmptyListProducesAnEmptyString() {
assertThat(this.singleLineFormat.format(Collections.<String>emptyList())).isEqualTo(""); assertThat(this.singleLineFormat.format(Collections.<String>emptyList())).isEqualTo("");
} }
@Test @Test
public void formattingNullProducesAnEmptyString() { void formattingNullProducesAnEmptyString() {
assertThat(this.singleLineFormat.format(null)).isEqualTo(""); assertThat(this.singleLineFormat.format(null)).isEqualTo("");
} }
@Test @Test
public void formattingASingleElement() { void formattingASingleElement() {
assertThat(this.singleLineFormat.format(Collections.singletonList("alpha"))).isEqualTo(" alpha"); assertThat(this.singleLineFormat.format(Collections.singletonList("alpha"))).isEqualTo(" alpha");
} }
@Test @Test
public void formattingMultipleElements() { void formattingMultipleElements() {
assertThat(this.singleLineFormat.format(Arrays.asList("alpha", "bravo"))).isEqualTo(" alpha bravo"); 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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with 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.io.IOException;
import java.util.Base64; 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.HttpHeaders;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.restdocs.AbstractSnippetTests; import org.springframework.restdocs.testfixtures.jupiter.AssertableSnippets;
import org.springframework.restdocs.templates.TemplateFormat; import org.springframework.restdocs.testfixtures.jupiter.OperationBuilder;
import org.springframework.restdocs.testfixtures.jupiter.RenderedSnippetTest;
import static org.assertj.core.api.Assertions.assertThat; 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 Paul-Christian Volkmer
* @author Tomasz Kopczynski * @author Tomasz Kopczynski
*/ */
@RunWith(Parameterized.class) class CurlRequestSnippetTests {
public class CurlRequestSnippetTests extends AbstractSnippetTests {
private CommandFormatter commandFormatter = CliDocumentation.singleLineFormat(); private CommandFormatter commandFormatter = CliDocumentation.singleLineFormat();
public CurlRequestSnippetTests(String name, TemplateFormat templateFormat) { @RenderedSnippetTest
super(name, templateFormat); void getRequest(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
}
@Test
public void getRequest() throws IOException {
new CurlRequestSnippet(this.commandFormatter) new CurlRequestSnippet(this.commandFormatter)
.document(this.operationBuilder.request("http://localhost/foo").build()); .document(operationBuilder.request("http://localhost/foo").build());
assertThat(this.generatedSnippets.curlRequest()) assertThat(snippets.curlRequest()).isCodeBlock(
.is(codeBlock("bash").withContent("$ curl 'http://localhost/foo' -i -X GET")); (codeBlock) -> codeBlock.withLanguage("bash").content("$ curl 'http://localhost/foo' -i -X GET"));
} }
@Test @RenderedSnippetTest
public void nonGetRequest() throws IOException { void nonGetRequest(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
new CurlRequestSnippet(this.commandFormatter) new CurlRequestSnippet(this.commandFormatter)
.document(this.operationBuilder.request("http://localhost/foo").method("POST").build()); .document(operationBuilder.request("http://localhost/foo").method("POST").build());
assertThat(this.generatedSnippets.curlRequest()) assertThat(snippets.curlRequest()).isCodeBlock(
.is(codeBlock("bash").withContent("$ curl 'http://localhost/foo' -i -X POST")); (codeBlock) -> codeBlock.withLanguage("bash").content("$ curl 'http://localhost/foo' -i -X POST"));
} }
@Test @RenderedSnippetTest
public void requestWithContent() throws IOException { void requestWithContent(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
new CurlRequestSnippet(this.commandFormatter) new CurlRequestSnippet(this.commandFormatter)
.document(this.operationBuilder.request("http://localhost/foo").content("content").build()); .document(operationBuilder.request("http://localhost/foo").content("content").build());
assertThat(this.generatedSnippets.curlRequest()) assertThat(snippets.curlRequest()).isCodeBlock((codeBlock) -> codeBlock.withLanguage("bash")
.is(codeBlock("bash").withContent("$ curl 'http://localhost/foo' -i -X GET -d 'content'")); .content("$ curl 'http://localhost/foo' -i -X GET -d 'content'"));
} }
@Test @RenderedSnippetTest
public void getRequestWithQueryString() throws IOException { void getRequestWithQueryString(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
new CurlRequestSnippet(this.commandFormatter) new CurlRequestSnippet(this.commandFormatter)
.document(this.operationBuilder.request("http://localhost/foo?param=value").build()); .document(operationBuilder.request("http://localhost/foo?param=value").build());
assertThat(this.generatedSnippets.curlRequest()) assertThat(snippets.curlRequest()).isCodeBlock((codeBlock) -> codeBlock.withLanguage("bash")
.is(codeBlock("bash").withContent("$ curl 'http://localhost/foo?param=value' -i -X GET")); .content("$ curl 'http://localhost/foo?param=value' -i -X GET"));
} }
@Test @RenderedSnippetTest
public void getRequestWithQueryStringWithNoValue() throws IOException { void getRequestWithQueryStringWithNoValue(OperationBuilder operationBuilder, AssertableSnippets snippets)
throws IOException {
new CurlRequestSnippet(this.commandFormatter) new CurlRequestSnippet(this.commandFormatter)
.document(this.operationBuilder.request("http://localhost/foo?param").build()); .document(operationBuilder.request("http://localhost/foo?param").build());
assertThat(this.generatedSnippets.curlRequest()) assertThat(snippets.curlRequest()).isCodeBlock(
.is(codeBlock("bash").withContent("$ curl 'http://localhost/foo?param' -i -X GET")); (codeBlock) -> codeBlock.withLanguage("bash").content("$ curl 'http://localhost/foo?param' -i -X GET"));
} }
@Test @RenderedSnippetTest
public void postRequestWithQueryString() throws IOException { void postRequestWithQueryString(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
new CurlRequestSnippet(this.commandFormatter) new CurlRequestSnippet(this.commandFormatter)
.document(this.operationBuilder.request("http://localhost/foo?param=value").method("POST").build()); .document(operationBuilder.request("http://localhost/foo?param=value").method("POST").build());
assertThat(this.generatedSnippets.curlRequest()) assertThat(snippets.curlRequest()).isCodeBlock((codeBlock) -> codeBlock.withLanguage("bash")
.is(codeBlock("bash").withContent("$ curl 'http://localhost/foo?param=value' -i -X POST")); .content("$ curl 'http://localhost/foo?param=value' -i -X POST"));
} }
@Test @RenderedSnippetTest
public void postRequestWithQueryStringWithNoValue() throws IOException { void postRequestWithQueryStringWithNoValue(OperationBuilder operationBuilder, AssertableSnippets snippets)
throws IOException {
new CurlRequestSnippet(this.commandFormatter) new CurlRequestSnippet(this.commandFormatter)
.document(this.operationBuilder.request("http://localhost/foo?param").method("POST").build()); .document(operationBuilder.request("http://localhost/foo?param").method("POST").build());
assertThat(this.generatedSnippets.curlRequest()) assertThat(snippets.curlRequest()).isCodeBlock((codeBlock) -> codeBlock.withLanguage("bash")
.is(codeBlock("bash").withContent("$ curl 'http://localhost/foo?param' -i -X POST")); .content("$ curl 'http://localhost/foo?param' -i -X POST"));
} }
@Test @RenderedSnippetTest
public void postRequestWithOneParameter() throws IOException { void postRequestWithOneParameter(OperationBuilder operationBuilder, AssertableSnippets snippets)
throws IOException {
new CurlRequestSnippet(this.commandFormatter) new CurlRequestSnippet(this.commandFormatter)
.document(this.operationBuilder.request("http://localhost/foo").method("POST").content("k1=v1").build()); .document(operationBuilder.request("http://localhost/foo").method("POST").content("k1=v1").build());
assertThat(this.generatedSnippets.curlRequest()) assertThat(snippets.curlRequest()).isCodeBlock((codeBlock) -> codeBlock.withLanguage("bash")
.is(codeBlock("bash").withContent("$ curl 'http://localhost/foo' -i -X POST -d 'k1=v1'")); .content("$ curl 'http://localhost/foo' -i -X POST -d 'k1=v1'"));
} }
@Test @RenderedSnippetTest
public void postRequestWithOneParameterAndExplicitContentType() throws IOException { void postRequestWithOneParameterAndExplicitContentType(OperationBuilder operationBuilder,
new CurlRequestSnippet(this.commandFormatter).document(this.operationBuilder.request("http://localhost/foo") AssertableSnippets snippets) throws IOException {
new CurlRequestSnippet(this.commandFormatter).document(operationBuilder.request("http://localhost/foo")
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED_VALUE) .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED_VALUE)
.method("POST") .method("POST")
.content("k1=v1") .content("k1=v1")
.build()); .build());
assertThat(this.generatedSnippets.curlRequest()) assertThat(snippets.curlRequest()).isCodeBlock((codeBlock) -> codeBlock.withLanguage("bash")
.is(codeBlock("bash").withContent("$ curl 'http://localhost/foo' -i -X POST -d 'k1=v1'")); .content("$ curl 'http://localhost/foo' -i -X POST -d 'k1=v1'"));
} }
@Test @RenderedSnippetTest
public void postRequestWithOneParameterWithNoValue() throws IOException { void postRequestWithOneParameterWithNoValue(OperationBuilder operationBuilder, AssertableSnippets snippets)
throws IOException {
new CurlRequestSnippet(this.commandFormatter) new CurlRequestSnippet(this.commandFormatter)
.document(this.operationBuilder.request("http://localhost/foo").method("POST").content("k1=").build()); .document(operationBuilder.request("http://localhost/foo").method("POST").content("k1=").build());
assertThat(this.generatedSnippets.curlRequest()) assertThat(snippets.curlRequest()).isCodeBlock((codeBlock) -> codeBlock.withLanguage("bash")
.is(codeBlock("bash").withContent("$ curl 'http://localhost/foo' -i -X POST -d 'k1='")); .content("$ curl 'http://localhost/foo' -i -X POST -d 'k1='"));
} }
@Test @RenderedSnippetTest
public void postRequestWithMultipleParameters() throws IOException { void postRequestWithMultipleParameters(OperationBuilder operationBuilder, AssertableSnippets snippets)
new CurlRequestSnippet(this.commandFormatter).document(this.operationBuilder.request("http://localhost/foo") throws IOException {
new CurlRequestSnippet(this.commandFormatter).document(operationBuilder.request("http://localhost/foo")
.method("POST") .method("POST")
.content("k1=v1&k1=v1-bis&k2=v2") .content("k1=v1&k1=v1-bis&k2=v2")
.build()); .build());
assertThat(this.generatedSnippets.curlRequest()).is(codeBlock("bash") assertThat(snippets.curlRequest()).isCodeBlock((codeBlock) -> codeBlock.withLanguage("bash")
.withContent("$ curl 'http://localhost/foo' -i -X POST" + " -d 'k1=v1&k1=v1-bis&k2=v2'")); .content("$ curl 'http://localhost/foo' -i -X POST" + " -d 'k1=v1&k1=v1-bis&k2=v2'"));
} }
@Test @RenderedSnippetTest
public void postRequestWithUrlEncodedParameter() throws IOException { void postRequestWithUrlEncodedParameter(OperationBuilder operationBuilder, AssertableSnippets snippets)
throws IOException {
new CurlRequestSnippet(this.commandFormatter) new CurlRequestSnippet(this.commandFormatter)
.document(this.operationBuilder.request("http://localhost/foo").method("POST").content("k1=a%26b").build()); .document(operationBuilder.request("http://localhost/foo").method("POST").content("k1=a%26b").build());
assertThat(this.generatedSnippets.curlRequest()) assertThat(snippets.curlRequest()).isCodeBlock((codeBlock) -> codeBlock.withLanguage("bash")
.is(codeBlock("bash").withContent("$ curl 'http://localhost/foo' -i -X POST -d 'k1=a%26b'")); .content("$ curl 'http://localhost/foo' -i -X POST -d 'k1=a%26b'"));
} }
@Test @RenderedSnippetTest
public void postRequestWithJsonData() throws IOException { void postRequestWithJsonData(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
new CurlRequestSnippet(this.commandFormatter).document(this.operationBuilder.request("http://localhost/foo") new CurlRequestSnippet(this.commandFormatter).document(operationBuilder.request("http://localhost/foo")
.method("POST") .method("POST")
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
.content("{\"a\":\"alpha\"}") .content("{\"a\":\"alpha\"}")
.build()); .build());
assertThat(this.generatedSnippets.curlRequest()).is(codeBlock("bash").withContent( assertThat(snippets.curlRequest()).isCodeBlock((codeBlock) -> codeBlock.withLanguage("bash")
"$ curl 'http://localhost/foo' -i -X POST -H 'Content-Type: application/json' -d '{\"a\":\"alpha\"}'")); .content(
"$ curl 'http://localhost/foo' -i -X POST -H 'Content-Type: application/json' -d '{\"a\":\"alpha\"}'"));
} }
@Test @RenderedSnippetTest
public void putRequestWithOneParameter() throws IOException { void putRequestWithOneParameter(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
new CurlRequestSnippet(this.commandFormatter) new CurlRequestSnippet(this.commandFormatter)
.document(this.operationBuilder.request("http://localhost/foo").method("PUT").content("k1=v1").build()); .document(operationBuilder.request("http://localhost/foo").method("PUT").content("k1=v1").build());
assertThat(this.generatedSnippets.curlRequest()) assertThat(snippets.curlRequest()).isCodeBlock((codeBlock) -> codeBlock.withLanguage("bash")
.is(codeBlock("bash").withContent("$ curl 'http://localhost/foo' -i -X PUT -d 'k1=v1'")); .content("$ curl 'http://localhost/foo' -i -X PUT -d 'k1=v1'"));
} }
@Test @RenderedSnippetTest
public void putRequestWithMultipleParameters() throws IOException { void putRequestWithMultipleParameters(OperationBuilder operationBuilder, AssertableSnippets snippets)
new CurlRequestSnippet(this.commandFormatter).document(this.operationBuilder.request("http://localhost/foo") throws IOException {
new CurlRequestSnippet(this.commandFormatter).document(operationBuilder.request("http://localhost/foo")
.method("PUT") .method("PUT")
.content("k1=v1&k1=v1-bis&k2=v2") .content("k1=v1&k1=v1-bis&k2=v2")
.build()); .build());
assertThat(this.generatedSnippets.curlRequest()).is(codeBlock("bash") assertThat(snippets.curlRequest()).isCodeBlock((codeBlock) -> codeBlock.withLanguage("bash")
.withContent("$ curl 'http://localhost/foo' -i -X PUT" + " -d 'k1=v1&k1=v1-bis&k2=v2'")); .content("$ curl 'http://localhost/foo' -i -X PUT" + " -d 'k1=v1&k1=v1-bis&k2=v2'"));
} }
@Test @RenderedSnippetTest
public void putRequestWithUrlEncodedParameter() throws IOException { void putRequestWithUrlEncodedParameter(OperationBuilder operationBuilder, AssertableSnippets snippets)
throws IOException {
new CurlRequestSnippet(this.commandFormatter) new CurlRequestSnippet(this.commandFormatter)
.document(this.operationBuilder.request("http://localhost/foo").method("PUT").content("k1=a%26b").build()); .document(operationBuilder.request("http://localhost/foo").method("PUT").content("k1=a%26b").build());
assertThat(this.generatedSnippets.curlRequest()) assertThat(snippets.curlRequest()).isCodeBlock((codeBlock) -> codeBlock.withLanguage("bash")
.is(codeBlock("bash").withContent("$ curl 'http://localhost/foo' -i -X PUT -d 'k1=a%26b'")); .content("$ curl 'http://localhost/foo' -i -X PUT -d 'k1=a%26b'"));
} }
@Test @RenderedSnippetTest
public void requestWithHeaders() throws IOException { void requestWithHeaders(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
new CurlRequestSnippet(this.commandFormatter).document(this.operationBuilder.request("http://localhost/foo") new CurlRequestSnippet(this.commandFormatter).document(operationBuilder.request("http://localhost/foo")
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
.header("a", "alpha") .header("a", "alpha")
.build()); .build());
assertThat(this.generatedSnippets.curlRequest()).is(codeBlock("bash").withContent( assertThat(snippets.curlRequest()).isCodeBlock((codeBlock) -> codeBlock.withLanguage("bash")
"$ curl 'http://localhost/foo' -i -X GET" + " -H 'Content-Type: application/json' -H 'a: alpha'")); .content("$ curl 'http://localhost/foo' -i -X GET" + " -H 'Content-Type: application/json' -H 'a: alpha'"));
} }
@Test @RenderedSnippetTest
public void requestWithHeadersMultiline() throws IOException { void requestWithHeadersMultiline(OperationBuilder operationBuilder, AssertableSnippets snippets)
throws IOException {
new CurlRequestSnippet(CliDocumentation.multiLineFormat()) 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(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
.header("a", "alpha") .header("a", "alpha")
.build()); .build());
assertThat(this.generatedSnippets.curlRequest()) assertThat(snippets.curlRequest()).isCodeBlock((codeBlock) -> codeBlock.withLanguage("bash")
.is(codeBlock("bash").withContent(String.format("$ curl 'http://localhost/foo' -i -X GET \\%n" .content(String.format("$ curl 'http://localhost/foo' -i -X GET \\%n"
+ " -H 'Content-Type: application/json' \\%n" + " -H 'a: alpha'"))); + " -H 'Content-Type: application/json' \\%n" + " -H 'a: alpha'")));
} }
@Test @RenderedSnippetTest
public void requestWithCookies() throws IOException { void requestWithCookies(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
new CurlRequestSnippet(this.commandFormatter).document(this.operationBuilder.request("http://localhost/foo") new CurlRequestSnippet(this.commandFormatter).document(operationBuilder.request("http://localhost/foo")
.cookie("name1", "value1") .cookie("name1", "value1")
.cookie("name2", "value2") .cookie("name2", "value2")
.build()); .build());
assertThat(this.generatedSnippets.curlRequest()).is(codeBlock("bash") assertThat(snippets.curlRequest()).isCodeBlock((codeBlock) -> codeBlock.withLanguage("bash")
.withContent("$ curl 'http://localhost/foo' -i -X GET" + " --cookie 'name1=value1;name2=value2'")); .content("$ curl 'http://localhost/foo' -i -X GET" + " --cookie 'name1=value1;name2=value2'"));
} }
@Test @RenderedSnippetTest
public void multipartPostWithNoSubmittedFileName() throws IOException { void multipartPostWithNoSubmittedFileName(OperationBuilder operationBuilder, AssertableSnippets snippets)
new CurlRequestSnippet(this.commandFormatter).document(this.operationBuilder.request("http://localhost/upload") throws IOException {
new CurlRequestSnippet(this.commandFormatter).document(operationBuilder.request("http://localhost/upload")
.method("POST") .method("POST")
.header(HttpHeaders.CONTENT_TYPE, MediaType.MULTIPART_FORM_DATA_VALUE) .header(HttpHeaders.CONTENT_TYPE, MediaType.MULTIPART_FORM_DATA_VALUE)
.part("metadata", "{\"description\": \"foo\"}".getBytes()) .part("metadata", "{\"description\": \"foo\"}".getBytes())
.build()); .build());
String expectedContent = "$ curl 'http://localhost/upload' -i -X POST -H " String expectedContent = "$ curl 'http://localhost/upload' -i -X POST -H "
+ "'Content-Type: multipart/form-data' -F " + "'metadata={\"description\": \"foo\"}'"; + "'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 @RenderedSnippetTest
public void multipartPostWithContentType() throws IOException { void multipartPostWithContentType(OperationBuilder operationBuilder, AssertableSnippets snippets)
new CurlRequestSnippet(this.commandFormatter).document(this.operationBuilder.request("http://localhost/upload") throws IOException {
new CurlRequestSnippet(this.commandFormatter).document(operationBuilder.request("http://localhost/upload")
.method("POST") .method("POST")
.header(HttpHeaders.CONTENT_TYPE, MediaType.MULTIPART_FORM_DATA_VALUE) .header(HttpHeaders.CONTENT_TYPE, MediaType.MULTIPART_FORM_DATA_VALUE)
.part("image", new byte[0]) .part("image", new byte[0])
@@ -242,12 +248,13 @@ public class CurlRequestSnippetTests extends AbstractSnippetTests {
.build()); .build());
String expectedContent = "$ curl 'http://localhost/upload' -i -X POST -H " String expectedContent = "$ curl 'http://localhost/upload' -i -X POST -H "
+ "'Content-Type: multipart/form-data' -F " + "'image=@documents/images/example.png;type=image/png'"; + "'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 @RenderedSnippetTest
public void multipartPost() throws IOException { void multipartPost(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
new CurlRequestSnippet(this.commandFormatter).document(this.operationBuilder.request("http://localhost/upload") new CurlRequestSnippet(this.commandFormatter).document(operationBuilder.request("http://localhost/upload")
.method("POST") .method("POST")
.header(HttpHeaders.CONTENT_TYPE, MediaType.MULTIPART_FORM_DATA_VALUE) .header(HttpHeaders.CONTENT_TYPE, MediaType.MULTIPART_FORM_DATA_VALUE)
.part("image", new byte[0]) .part("image", new byte[0])
@@ -255,36 +262,38 @@ public class CurlRequestSnippetTests extends AbstractSnippetTests {
.build()); .build());
String expectedContent = "$ curl 'http://localhost/upload' -i -X POST -H " String expectedContent = "$ curl 'http://localhost/upload' -i -X POST -H "
+ "'Content-Type: multipart/form-data' -F " + "'image=@documents/images/example.png'"; + "'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 @RenderedSnippetTest
public void basicAuthCredentialsAreSuppliedUsingUserOption() throws IOException { void basicAuthCredentialsAreSuppliedUsingUserOption(OperationBuilder operationBuilder, AssertableSnippets snippets)
new CurlRequestSnippet(this.commandFormatter).document(this.operationBuilder.request("http://localhost/foo") throws IOException {
new CurlRequestSnippet(this.commandFormatter).document(operationBuilder.request("http://localhost/foo")
.header(HttpHeaders.AUTHORIZATION, "Basic " + Base64.getEncoder().encodeToString("user:secret".getBytes())) .header(HttpHeaders.AUTHORIZATION, "Basic " + Base64.getEncoder().encodeToString("user:secret".getBytes()))
.build()); .build());
assertThat(this.generatedSnippets.curlRequest()) assertThat(snippets.curlRequest()).isCodeBlock((codeBlock) -> codeBlock.withLanguage("bash")
.is(codeBlock("bash").withContent("$ curl 'http://localhost/foo' -i -u 'user:secret' -X GET")); .content("$ curl 'http://localhost/foo' -i -u 'user:secret' -X GET"));
} }
@Test @RenderedSnippetTest
public void customAttributes() throws IOException { void customAttributes(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
new CurlRequestSnippet(this.commandFormatter).document(this.operationBuilder.request("http://localhost/foo") new CurlRequestSnippet(this.commandFormatter).document(operationBuilder.request("http://localhost/foo")
.header(HttpHeaders.HOST, "api.example.com") .header(HttpHeaders.HOST, "api.example.com")
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
.header("a", "alpha") .header("a", "alpha")
.build()); .build());
assertThat(this.generatedSnippets.curlRequest()) assertThat(snippets.curlRequest()).isCodeBlock((codeBlock) -> codeBlock.withLanguage("bash")
.is(codeBlock("bash").withContent("$ curl 'http://localhost/foo' -i -X GET -H 'Host: api.example.com'" .content("$ curl 'http://localhost/foo' -i -X GET -H 'Host: api.example.com'"
+ " -H 'Content-Type: application/json' -H 'a: alpha'")); + " -H 'Content-Type: application/json' -H 'a: alpha'"));
} }
@Test @RenderedSnippetTest
public void deleteWithQueryString() throws IOException { void deleteWithQueryString(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
new CurlRequestSnippet(this.commandFormatter) new CurlRequestSnippet(this.commandFormatter)
.document(this.operationBuilder.request("http://localhost/foo?a=alpha&b=bravo").method("DELETE").build()); .document(operationBuilder.request("http://localhost/foo?a=alpha&b=bravo").method("DELETE").build());
assertThat(this.generatedSnippets.curlRequest()) assertThat(snippets.curlRequest()).isCodeBlock((codeBlock) -> codeBlock.withLanguage("bash")
.is(codeBlock("bash").withContent("$ curl 'http://localhost/foo?a=alpha&b=bravo' -i " + "-X DELETE")); .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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with 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.io.IOException;
import java.util.Base64; 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.HttpHeaders;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.restdocs.AbstractSnippetTests; import org.springframework.restdocs.testfixtures.jupiter.AssertableSnippets;
import org.springframework.restdocs.templates.TemplateFormat; import org.springframework.restdocs.testfixtures.jupiter.OperationBuilder;
import org.springframework.restdocs.testfixtures.jupiter.RenderedSnippetTest;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
@@ -41,249 +38,257 @@ import static org.assertj.core.api.Assertions.assertThat;
* @author Raman Gupta * @author Raman Gupta
* @author Tomasz Kopczynski * @author Tomasz Kopczynski
*/ */
@RunWith(Parameterized.class) class HttpieRequestSnippetTests {
public class HttpieRequestSnippetTests extends AbstractSnippetTests {
private CommandFormatter commandFormatter = CliDocumentation.singleLineFormat(); private CommandFormatter commandFormatter = CliDocumentation.singleLineFormat();
public HttpieRequestSnippetTests(String name, TemplateFormat templateFormat) { @RenderedSnippetTest
super(name, templateFormat); void getRequest(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
}
@Test
public void getRequest() throws IOException {
new HttpieRequestSnippet(this.commandFormatter) new HttpieRequestSnippet(this.commandFormatter)
.document(this.operationBuilder.request("http://localhost/foo").build()); .document(operationBuilder.request("http://localhost/foo").build());
assertThat(this.generatedSnippets.httpieRequest()) assertThat(snippets.httpieRequest())
.is(codeBlock("bash").withContent("$ http GET 'http://localhost/foo'")); .isCodeBlock((codeBlock) -> codeBlock.withLanguage("bash").content("$ http GET 'http://localhost/foo'"));
} }
@Test @RenderedSnippetTest
public void nonGetRequest() throws IOException { void nonGetRequest(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
new HttpieRequestSnippet(this.commandFormatter) new HttpieRequestSnippet(this.commandFormatter)
.document(this.operationBuilder.request("http://localhost/foo").method("POST").build()); .document(operationBuilder.request("http://localhost/foo").method("POST").build());
assertThat(this.generatedSnippets.httpieRequest()) assertThat(snippets.httpieRequest())
.is(codeBlock("bash").withContent("$ http POST 'http://localhost/foo'")); .isCodeBlock((codeBlock) -> codeBlock.withLanguage("bash").content("$ http POST 'http://localhost/foo'"));
} }
@Test @RenderedSnippetTest
public void requestWithContent() throws IOException { void requestWithContent(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
new HttpieRequestSnippet(this.commandFormatter) new HttpieRequestSnippet(this.commandFormatter)
.document(this.operationBuilder.request("http://localhost/foo").content("content").build()); .document(operationBuilder.request("http://localhost/foo").content("content").build());
assertThat(this.generatedSnippets.httpieRequest()) assertThat(snippets.httpieRequest()).isCodeBlock((codeBlock) -> codeBlock.withLanguage("bash")
.is(codeBlock("bash").withContent("$ echo 'content' | http GET 'http://localhost/foo'")); .content("$ echo 'content' | http GET 'http://localhost/foo'"));
} }
@Test @RenderedSnippetTest
public void getRequestWithQueryString() throws IOException { void getRequestWithQueryString(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
new HttpieRequestSnippet(this.commandFormatter) new HttpieRequestSnippet(this.commandFormatter)
.document(this.operationBuilder.request("http://localhost/foo?param=value").build()); .document(operationBuilder.request("http://localhost/foo?param=value").build());
assertThat(this.generatedSnippets.httpieRequest()) assertThat(snippets.httpieRequest()).isCodeBlock(
.is(codeBlock("bash").withContent("$ http GET 'http://localhost/foo?param=value'")); (codeBlock) -> codeBlock.withLanguage("bash").content("$ http GET 'http://localhost/foo?param=value'"));
} }
@Test @RenderedSnippetTest
public void getRequestWithQueryStringWithNoValue() throws IOException { void getRequestWithQueryStringWithNoValue(OperationBuilder operationBuilder, AssertableSnippets snippets)
throws IOException {
new HttpieRequestSnippet(this.commandFormatter) new HttpieRequestSnippet(this.commandFormatter)
.document(this.operationBuilder.request("http://localhost/foo?param").build()); .document(operationBuilder.request("http://localhost/foo?param").build());
assertThat(this.generatedSnippets.httpieRequest()) assertThat(snippets.httpieRequest()).isCodeBlock(
.is(codeBlock("bash").withContent("$ http GET 'http://localhost/foo?param'")); (codeBlock) -> codeBlock.withLanguage("bash").content("$ http GET 'http://localhost/foo?param'"));
} }
@Test @RenderedSnippetTest
public void postRequestWithQueryString() throws IOException { void postRequestWithQueryString(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
new HttpieRequestSnippet(this.commandFormatter) new HttpieRequestSnippet(this.commandFormatter)
.document(this.operationBuilder.request("http://localhost/foo?param=value").method("POST").build()); .document(operationBuilder.request("http://localhost/foo?param=value").method("POST").build());
assertThat(this.generatedSnippets.httpieRequest()) assertThat(snippets.httpieRequest()).isCodeBlock((codeBlock) -> codeBlock.withLanguage("bash")
.is(codeBlock("bash").withContent("$ http POST 'http://localhost/foo?param=value'")); .content("$ http POST 'http://localhost/foo?param=value'"));
} }
@Test @RenderedSnippetTest
public void postRequestWithQueryStringWithNoValue() throws IOException { void postRequestWithQueryStringWithNoValue(OperationBuilder operationBuilder, AssertableSnippets snippets)
throws IOException {
new HttpieRequestSnippet(this.commandFormatter) new HttpieRequestSnippet(this.commandFormatter)
.document(this.operationBuilder.request("http://localhost/foo?param").method("POST").build()); .document(operationBuilder.request("http://localhost/foo?param").method("POST").build());
assertThat(this.generatedSnippets.httpieRequest()) assertThat(snippets.httpieRequest()).isCodeBlock(
.is(codeBlock("bash").withContent("$ http POST 'http://localhost/foo?param'")); (codeBlock) -> codeBlock.withLanguage("bash").content("$ http POST 'http://localhost/foo?param'"));
} }
@Test @RenderedSnippetTest
public void postRequestWithOneParameter() throws IOException { void postRequestWithOneParameter(OperationBuilder operationBuilder, AssertableSnippets snippets)
new HttpieRequestSnippet(this.commandFormatter).document(this.operationBuilder.request("http://localhost/foo") throws IOException {
new HttpieRequestSnippet(this.commandFormatter).document(operationBuilder.request("http://localhost/foo")
.method("POST") .method("POST")
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED_VALUE) .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED_VALUE)
.content("k1=v1") .content("k1=v1")
.build()); .build());
assertThat(this.generatedSnippets.httpieRequest()) assertThat(snippets.httpieRequest()).isCodeBlock((codeBlock) -> codeBlock.withLanguage("bash")
.is(codeBlock("bash").withContent("$ http --form POST 'http://localhost/foo' 'k1=v1'")); .content("$ http --form POST 'http://localhost/foo' 'k1=v1'"));
} }
@Test @RenderedSnippetTest
public void postRequestWithOneParameterWithNoValue() throws IOException { void postRequestWithOneParameterWithNoValue(OperationBuilder operationBuilder, AssertableSnippets snippets)
new HttpieRequestSnippet(this.commandFormatter).document(this.operationBuilder.request("http://localhost/foo") throws IOException {
new HttpieRequestSnippet(this.commandFormatter).document(operationBuilder.request("http://localhost/foo")
.method("POST") .method("POST")
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED_VALUE) .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED_VALUE)
.content("k1") .content("k1")
.build()); .build());
assertThat(this.generatedSnippets.httpieRequest()) assertThat(snippets.httpieRequest()).isCodeBlock((codeBlock) -> codeBlock.withLanguage("bash")
.is(codeBlock("bash").withContent("$ http --form POST 'http://localhost/foo' 'k1='")); .content("$ http --form POST 'http://localhost/foo' 'k1='"));
} }
@Test @RenderedSnippetTest
public void postRequestWithMultipleParameters() throws IOException { void postRequestWithMultipleParameters(OperationBuilder operationBuilder, AssertableSnippets snippets)
new HttpieRequestSnippet(this.commandFormatter).document(this.operationBuilder.request("http://localhost/foo") throws IOException {
new HttpieRequestSnippet(this.commandFormatter).document(operationBuilder.request("http://localhost/foo")
.method("POST") .method("POST")
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED_VALUE) .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED_VALUE)
.content("k1=v1&k1=v1-bis&k2=v2") .content("k1=v1&k1=v1-bis&k2=v2")
.build()); .build());
assertThat(this.generatedSnippets.httpieRequest()) assertThat(snippets.httpieRequest()).isCodeBlock((codeBlock) -> codeBlock.withLanguage("bash")
.is(codeBlock("bash").withContent("$ http --form POST 'http://localhost/foo' 'k1=v1' 'k1=v1-bis' 'k2=v2'")); .content("$ http --form POST 'http://localhost/foo' 'k1=v1' 'k1=v1-bis' 'k2=v2'"));
} }
@Test @RenderedSnippetTest
public void postRequestWithUrlEncodedParameter() throws IOException { void postRequestWithUrlEncodedParameter(OperationBuilder operationBuilder, AssertableSnippets snippets)
new HttpieRequestSnippet(this.commandFormatter).document(this.operationBuilder.request("http://localhost/foo") throws IOException {
new HttpieRequestSnippet(this.commandFormatter).document(operationBuilder.request("http://localhost/foo")
.method("POST") .method("POST")
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED_VALUE) .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED_VALUE)
.content("k1=a%26b") .content("k1=a%26b")
.build()); .build());
assertThat(this.generatedSnippets.httpieRequest()) assertThat(snippets.httpieRequest()).isCodeBlock((codeBlock) -> codeBlock.withLanguage("bash")
.is(codeBlock("bash").withContent("$ http --form POST 'http://localhost/foo' 'k1=a&b'")); .content("$ http --form POST 'http://localhost/foo' 'k1=a&b'"));
} }
@Test @RenderedSnippetTest
public void putRequestWithOneParameter() throws IOException { void putRequestWithOneParameter(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
new HttpieRequestSnippet(this.commandFormatter).document(this.operationBuilder.request("http://localhost/foo") new HttpieRequestSnippet(this.commandFormatter).document(operationBuilder.request("http://localhost/foo")
.method("PUT") .method("PUT")
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED_VALUE) .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED_VALUE)
.content("k1=v1") .content("k1=v1")
.build()); .build());
assertThat(this.generatedSnippets.httpieRequest()) assertThat(snippets.httpieRequest()).isCodeBlock((codeBlock) -> codeBlock.withLanguage("bash")
.is(codeBlock("bash").withContent("$ http --form PUT 'http://localhost/foo' 'k1=v1'")); .content("$ http --form PUT 'http://localhost/foo' 'k1=v1'"));
} }
@Test @RenderedSnippetTest
public void putRequestWithMultipleParameters() throws IOException { void putRequestWithMultipleParameters(OperationBuilder operationBuilder, AssertableSnippets snippets)
new HttpieRequestSnippet(this.commandFormatter).document(this.operationBuilder.request("http://localhost/foo") throws IOException {
new HttpieRequestSnippet(this.commandFormatter).document(operationBuilder.request("http://localhost/foo")
.method("PUT") .method("PUT")
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED_VALUE) .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED_VALUE)
.content("k1=v1&k1=v1-bis&k2=v2") .content("k1=v1&k1=v1-bis&k2=v2")
.build()); .build());
assertThat(this.generatedSnippets.httpieRequest()).is(codeBlock("bash") assertThat(snippets.httpieRequest()).isCodeBlock((codeBlock) -> codeBlock.withLanguage("bash")
.withContent("$ http --form PUT 'http://localhost/foo'" + " 'k1=v1' 'k1=v1-bis' 'k2=v2'")); .content("$ http --form PUT 'http://localhost/foo'" + " 'k1=v1' 'k1=v1-bis' 'k2=v2'"));
} }
@Test @RenderedSnippetTest
public void putRequestWithUrlEncodedParameter() throws IOException { void putRequestWithUrlEncodedParameter(OperationBuilder operationBuilder, AssertableSnippets snippets)
new HttpieRequestSnippet(this.commandFormatter).document(this.operationBuilder.request("http://localhost/foo") throws IOException {
new HttpieRequestSnippet(this.commandFormatter).document(operationBuilder.request("http://localhost/foo")
.method("PUT") .method("PUT")
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED_VALUE) .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED_VALUE)
.content("k1=a%26b") .content("k1=a%26b")
.build()); .build());
assertThat(this.generatedSnippets.httpieRequest()) assertThat(snippets.httpieRequest()).isCodeBlock((codeBlock) -> codeBlock.withLanguage("bash")
.is(codeBlock("bash").withContent("$ http --form PUT 'http://localhost/foo' 'k1=a&b'")); .content("$ http --form PUT 'http://localhost/foo' 'k1=a&b'"));
} }
@Test @RenderedSnippetTest
public void requestWithHeaders() throws IOException { void requestWithHeaders(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
new HttpieRequestSnippet(this.commandFormatter).document(this.operationBuilder.request("http://localhost/foo") new HttpieRequestSnippet(this.commandFormatter).document(operationBuilder.request("http://localhost/foo")
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
.header("a", "alpha") .header("a", "alpha")
.build()); .build());
assertThat(this.generatedSnippets.httpieRequest()).is(codeBlock("bash") assertThat(snippets.httpieRequest()).isCodeBlock((codeBlock) -> codeBlock.withLanguage("bash")
.withContent("$ http GET 'http://localhost/foo'" + " 'Content-Type:application/json' 'a:alpha'")); .content(("$ http GET 'http://localhost/foo'" + " 'Content-Type:application/json' 'a:alpha'")));
} }
@Test @RenderedSnippetTest
public void requestWithHeadersMultiline() throws IOException { void requestWithHeadersMultiline(OperationBuilder operationBuilder, AssertableSnippets snippets)
throws IOException {
new HttpieRequestSnippet(CliDocumentation.multiLineFormat()) 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(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
.header("a", "alpha") .header("a", "alpha")
.build()); .build());
assertThat(this.generatedSnippets.httpieRequest()).is(codeBlock("bash").withContent(String.format( assertThat(snippets.httpieRequest()).isCodeBlock((codeBlock) -> codeBlock.withLanguage("bash")
"$ http GET 'http://localhost/foo' \\%n" + " 'Content-Type:application/json' \\%n 'a:alpha'"))); .content(String.format("$ http GET 'http://localhost/foo' \\%n"
+ " 'Content-Type:application/json' \\%n 'a:alpha'")));
} }
@Test @RenderedSnippetTest
public void requestWithCookies() throws IOException { void requestWithCookies(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
new HttpieRequestSnippet(this.commandFormatter).document(this.operationBuilder.request("http://localhost/foo") new HttpieRequestSnippet(this.commandFormatter).document(operationBuilder.request("http://localhost/foo")
.cookie("name1", "value1") .cookie("name1", "value1")
.cookie("name2", "value2") .cookie("name2", "value2")
.build()); .build());
assertThat(this.generatedSnippets.httpieRequest()).is(codeBlock("bash") assertThat(snippets.httpieRequest()).isCodeBlock((codeBlock) -> codeBlock.withLanguage("bash")
.withContent("$ http GET 'http://localhost/foo'" + " 'Cookie:name1=value1' 'Cookie:name2=value2'")); .content(("$ http GET 'http://localhost/foo'" + " 'Cookie:name1=value1' 'Cookie:name2=value2'")));
} }
@Test @RenderedSnippetTest
public void multipartPostWithNoSubmittedFileName() throws IOException { void multipartPostWithNoSubmittedFileName(OperationBuilder operationBuilder, AssertableSnippets snippets)
new HttpieRequestSnippet(this.commandFormatter) throws IOException {
.document(this.operationBuilder.request("http://localhost/upload") new HttpieRequestSnippet(this.commandFormatter).document(operationBuilder.request("http://localhost/upload")
.method("POST") .method("POST")
.header(HttpHeaders.CONTENT_TYPE, MediaType.MULTIPART_FORM_DATA_VALUE) .header(HttpHeaders.CONTENT_TYPE, MediaType.MULTIPART_FORM_DATA_VALUE)
.part("metadata", "{\"description\": \"foo\"}".getBytes()) .part("metadata", "{\"description\": \"foo\"}".getBytes())
.build()); .build());
String expectedContent = "$ http --multipart POST 'http://localhost/upload'" String expectedContent = "$ http --multipart POST 'http://localhost/upload'"
+ " 'metadata'='{\"description\": \"foo\"}'"; + " 'metadata'='{\"description\": \"foo\"}'";
assertThat(this.generatedSnippets.httpieRequest()).is(codeBlock("bash").withContent(expectedContent)); assertThat(snippets.httpieRequest())
.isCodeBlock((codeBlock) -> codeBlock.withLanguage("bash").content(expectedContent));
} }
@Test @RenderedSnippetTest
public void multipartPostWithContentType() throws IOException { void multipartPostWithContentType(OperationBuilder operationBuilder, AssertableSnippets snippets)
new HttpieRequestSnippet(this.commandFormatter) throws IOException {
.document(this.operationBuilder.request("http://localhost/upload") new HttpieRequestSnippet(this.commandFormatter).document(operationBuilder.request("http://localhost/upload")
.method("POST") .method("POST")
.header(HttpHeaders.CONTENT_TYPE, MediaType.MULTIPART_FORM_DATA_VALUE) .header(HttpHeaders.CONTENT_TYPE, MediaType.MULTIPART_FORM_DATA_VALUE)
.part("image", new byte[0]) .part("image", new byte[0])
.header(HttpHeaders.CONTENT_TYPE, MediaType.IMAGE_PNG_VALUE) .header(HttpHeaders.CONTENT_TYPE, MediaType.IMAGE_PNG_VALUE)
.submittedFileName("documents/images/example.png") .submittedFileName("documents/images/example.png")
.build()); .build());
// httpie does not yet support manually set content type by part // httpie does not yet support manually set content type by part
String expectedContent = "$ http --multipart POST 'http://localhost/upload'" String expectedContent = "$ http --multipart POST 'http://localhost/upload'"
+ " 'image'@'documents/images/example.png'"; + " '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 @RenderedSnippetTest
public void multipartPost() throws IOException { void multipartPost(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
new HttpieRequestSnippet(this.commandFormatter) new HttpieRequestSnippet(this.commandFormatter).document(operationBuilder.request("http://localhost/upload")
.document(this.operationBuilder.request("http://localhost/upload") .method("POST")
.method("POST") .header(HttpHeaders.CONTENT_TYPE, MediaType.MULTIPART_FORM_DATA_VALUE)
.header(HttpHeaders.CONTENT_TYPE, MediaType.MULTIPART_FORM_DATA_VALUE) .part("image", new byte[0])
.part("image", new byte[0]) .submittedFileName("documents/images/example.png")
.submittedFileName("documents/images/example.png") .build());
.build());
String expectedContent = "$ http --multipart POST 'http://localhost/upload'" String expectedContent = "$ http --multipart POST 'http://localhost/upload'"
+ " 'image'@'documents/images/example.png'"; + " '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 @RenderedSnippetTest
public void basicAuthCredentialsAreSuppliedUsingAuthOption() throws IOException { void basicAuthCredentialsAreSuppliedUsingAuthOption(OperationBuilder operationBuilder, AssertableSnippets snippets)
new HttpieRequestSnippet(this.commandFormatter).document(this.operationBuilder.request("http://localhost/foo") throws IOException {
new HttpieRequestSnippet(this.commandFormatter).document(operationBuilder.request("http://localhost/foo")
.header(HttpHeaders.AUTHORIZATION, "Basic " + Base64.getEncoder().encodeToString("user:secret".getBytes())) .header(HttpHeaders.AUTHORIZATION, "Basic " + Base64.getEncoder().encodeToString("user:secret".getBytes()))
.build()); .build());
assertThat(this.generatedSnippets.httpieRequest()) assertThat(snippets.httpieRequest()).isCodeBlock((codeBlock) -> codeBlock.withLanguage("bash")
.is(codeBlock("bash").withContent("$ http --auth 'user:secret' GET 'http://localhost/foo'")); .content("$ http --auth 'user:secret' GET 'http://localhost/foo'"));
} }
@Test @RenderedSnippetTest
public void customAttributes() throws IOException { void customAttributes(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
new HttpieRequestSnippet(this.commandFormatter).document(this.operationBuilder.request("http://localhost/foo") new HttpieRequestSnippet(this.commandFormatter).document(operationBuilder.request("http://localhost/foo")
.header(HttpHeaders.HOST, "api.example.com") .header(HttpHeaders.HOST, "api.example.com")
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
.header("a", "alpha") .header("a", "alpha")
.build()); .build());
assertThat(this.generatedSnippets.httpieRequest()) assertThat(snippets.httpieRequest()).isCodeBlock((codeBlock) -> codeBlock.withLanguage("bash")
.is(codeBlock("bash").withContent("$ http GET 'http://localhost/foo' 'Host:api.example.com'" .content("$ http GET 'http://localhost/foo' 'Host:api.example.com'"
+ " 'Content-Type:application/json' 'a:alpha'")); + " 'Content-Type:application/json' 'a:alpha'"));
} }
@Test @RenderedSnippetTest
public void deleteWithQueryString() throws IOException { void deleteWithQueryString(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
new HttpieRequestSnippet(this.commandFormatter) new HttpieRequestSnippet(this.commandFormatter)
.document(this.operationBuilder.request("http://localhost/foo?a=alpha&b=bravo").method("DELETE").build()); .document(operationBuilder.request("http://localhost/foo?a=alpha&b=bravo").method("DELETE").build());
assertThat(this.generatedSnippets.httpieRequest()) assertThat(snippets.httpieRequest()).isCodeBlock((codeBlock) -> codeBlock.withLanguage("bash")
.is(codeBlock("bash").withContent("$ http DELETE 'http://localhost/foo?a=alpha&b=bravo'")); .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.List;
import java.util.Map; import java.util.Map;
import org.junit.Test; import org.junit.jupiter.api.Test;
import org.springframework.http.HttpHeaders; import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod; import org.springframework.http.HttpMethod;
@@ -63,13 +63,13 @@ import static org.mockito.Mockito.mock;
* @author Andy Wilkinson * @author Andy Wilkinson
* @author Filip Hrisafov * @author Filip Hrisafov
*/ */
public class RestDocumentationConfigurerTests { class RestDocumentationConfigurerTests {
private final TestRestDocumentationConfigurer configurer = new TestRestDocumentationConfigurer(); private final TestRestDocumentationConfigurer configurer = new TestRestDocumentationConfigurer();
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
@Test @Test
public void defaultConfiguration() { void defaultConfiguration() {
Map<String, Object> configuration = new HashMap<>(); Map<String, Object> configuration = new HashMap<>();
this.configurer.apply(configuration, createContext()); this.configurer.apply(configuration, createContext());
assertThat(configuration).containsKey(TemplateEngine.class.getName()); assertThat(configuration).containsKey(TemplateEngine.class.getName());
@@ -102,7 +102,7 @@ public class RestDocumentationConfigurerTests {
} }
@Test @Test
public void customTemplateEngine() { void customTemplateEngine() {
Map<String, Object> configuration = new HashMap<>(); Map<String, Object> configuration = new HashMap<>();
TemplateEngine templateEngine = mock(TemplateEngine.class); TemplateEngine templateEngine = mock(TemplateEngine.class);
this.configurer.templateEngine(templateEngine).apply(configuration, createContext()); this.configurer.templateEngine(templateEngine).apply(configuration, createContext());
@@ -110,7 +110,7 @@ public class RestDocumentationConfigurerTests {
} }
@Test @Test
public void customWriterResolver() { void customWriterResolver() {
Map<String, Object> configuration = new HashMap<>(); Map<String, Object> configuration = new HashMap<>();
WriterResolver writerResolver = mock(WriterResolver.class); WriterResolver writerResolver = mock(WriterResolver.class);
this.configurer.writerResolver(writerResolver).apply(configuration, createContext()); this.configurer.writerResolver(writerResolver).apply(configuration, createContext());
@@ -118,7 +118,7 @@ public class RestDocumentationConfigurerTests {
} }
@Test @Test
public void customDefaultSnippets() { void customDefaultSnippets() {
Map<String, Object> configuration = new HashMap<>(); Map<String, Object> configuration = new HashMap<>();
this.configurer.snippets().withDefaults(CliDocumentation.curlRequest()).apply(configuration, createContext()); this.configurer.snippets().withDefaults(CliDocumentation.curlRequest()).apply(configuration, createContext());
assertThat(configuration).containsKey(RestDocumentationGenerator.ATTRIBUTE_NAME_DEFAULT_SNIPPETS); assertThat(configuration).containsKey(RestDocumentationGenerator.ATTRIBUTE_NAME_DEFAULT_SNIPPETS);
@@ -133,7 +133,7 @@ public class RestDocumentationConfigurerTests {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
@Test @Test
public void additionalDefaultSnippets() { void additionalDefaultSnippets() {
Map<String, Object> configuration = new HashMap<>(); Map<String, Object> configuration = new HashMap<>();
Snippet snippet = mock(Snippet.class); Snippet snippet = mock(Snippet.class);
this.configurer.snippets().withAdditionalDefaults(snippet).apply(configuration, createContext()); this.configurer.snippets().withAdditionalDefaults(snippet).apply(configuration, createContext());
@@ -148,7 +148,7 @@ public class RestDocumentationConfigurerTests {
} }
@Test @Test
public void customSnippetEncoding() { void customSnippetEncoding() {
Map<String, Object> configuration = new HashMap<>(); Map<String, Object> configuration = new HashMap<>();
this.configurer.snippets().withEncoding("ISO-8859-1"); this.configurer.snippets().withEncoding("ISO-8859-1");
this.configurer.apply(configuration, createContext()); this.configurer.apply(configuration, createContext());
@@ -162,7 +162,7 @@ public class RestDocumentationConfigurerTests {
} }
@Test @Test
public void customTemplateFormat() { void customTemplateFormat() {
Map<String, Object> configuration = new HashMap<>(); Map<String, Object> configuration = new HashMap<>();
this.configurer.snippets().withTemplateFormat(TemplateFormats.markdown()).apply(configuration, createContext()); this.configurer.snippets().withTemplateFormat(TemplateFormats.markdown()).apply(configuration, createContext());
assertThat(configuration).containsKey(SnippetConfiguration.class.getName()); assertThat(configuration).containsKey(SnippetConfiguration.class.getName());
@@ -174,7 +174,7 @@ public class RestDocumentationConfigurerTests {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
@Test @Test
public void asciidoctorTableCellContentLambaIsInstalledWhenUsingAsciidoctorTemplateFormat() { void asciidoctorTableCellContentLambaIsInstalledWhenUsingAsciidoctorTemplateFormat() {
Map<String, Object> configuration = new HashMap<>(); Map<String, Object> configuration = new HashMap<>();
this.configurer.apply(configuration, createContext()); this.configurer.apply(configuration, createContext());
TemplateEngine templateEngine = (TemplateEngine) configuration.get(TemplateEngine.class.getName()); TemplateEngine templateEngine = (TemplateEngine) configuration.get(TemplateEngine.class.getName());
@@ -187,7 +187,7 @@ public class RestDocumentationConfigurerTests {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
@Test @Test
public void asciidoctorTableCellContentLambaIsNotInstalledWhenUsingNonAsciidoctorTemplateFormat() { void asciidoctorTableCellContentLambaIsNotInstalledWhenUsingNonAsciidoctorTemplateFormat() {
Map<String, Object> configuration = new HashMap<>(); Map<String, Object> configuration = new HashMap<>();
this.configurer.snippetConfigurer.withTemplateFormat(TemplateFormats.markdown()); this.configurer.snippetConfigurer.withTemplateFormat(TemplateFormats.markdown());
this.configurer.apply(configuration, createContext()); this.configurer.apply(configuration, createContext());
@@ -199,7 +199,7 @@ public class RestDocumentationConfigurerTests {
} }
@Test @Test
public void customDefaultOperationRequestPreprocessor() { void customDefaultOperationRequestPreprocessor() {
Map<String, Object> configuration = new HashMap<>(); Map<String, Object> configuration = new HashMap<>();
this.configurer.operationPreprocessors() this.configurer.operationPreprocessors()
.withRequestDefaults(Preprocessors.prettyPrint(), Preprocessors.modifyHeaders().remove("Foo")) .withRequestDefaults(Preprocessors.prettyPrint(), Preprocessors.modifyHeaders().remove("Foo"))
@@ -214,7 +214,7 @@ public class RestDocumentationConfigurerTests {
} }
@Test @Test
public void customDefaultOperationResponsePreprocessor() { void customDefaultOperationResponsePreprocessor() {
Map<String, Object> configuration = new HashMap<>(); Map<String, Object> configuration = new HashMap<>();
this.configurer.operationPreprocessors() this.configurer.operationPreprocessors()
.withResponseDefaults(Preprocessors.prettyPrint(), Preprocessors.modifyHeaders().remove("Foo")) .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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with 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.Arrays;
import java.util.Collections; 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.assertj.core.api.Assertions.assertThat;
import static org.mockito.BDDMockito.given; import static org.mockito.BDDMockito.given;
@@ -30,7 +30,7 @@ import static org.mockito.Mockito.mock;
* *
* @author Andy Wilkinson * @author Andy Wilkinson
*/ */
public class ConstraintDescriptionsTests { class ConstraintDescriptionsTests {
private final ConstraintResolver constraintResolver = mock(ConstraintResolver.class); private final ConstraintResolver constraintResolver = mock(ConstraintResolver.class);
@@ -41,7 +41,7 @@ public class ConstraintDescriptionsTests {
this.constraintResolver, this.constraintDescriptionResolver); this.constraintResolver, this.constraintDescriptionResolver);
@Test @Test
public void descriptionsForConstraints() { void descriptionsForConstraints() {
Constraint constraint1 = new Constraint("constraint1", Collections.<String, Object>emptyMap()); Constraint constraint1 = new Constraint("constraint1", Collections.<String, Object>emptyMap());
Constraint constraint2 = new Constraint("constraint2", Collections.<String, Object>emptyMap()); Constraint constraint2 = new Constraint("constraint2", Collections.<String, Object>emptyMap());
given(this.constraintResolver.resolveForProperty("foo", Constrained.class)) given(this.constraintResolver.resolveForProperty("foo", Constrained.class))
@@ -52,7 +52,7 @@ public class ConstraintDescriptionsTests {
} }
@Test @Test
public void emptyListOfDescriptionsWhenThereAreNoConstraints() { void emptyListOfDescriptionsWhenThereAreNoConstraints() {
given(this.constraintResolver.resolveForProperty("foo", Constrained.class)) given(this.constraintResolver.resolveForProperty("foo", Constrained.class))
.willReturn(Collections.<Constraint>emptyList()); .willReturn(Collections.<Constraint>emptyList());
assertThat(this.constraintDescriptions.descriptionsForProperty("foo").size()).isEqualTo(0); 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.Mod10Check;
import org.hibernate.validator.constraints.Mod11Check; import org.hibernate.validator.constraints.Mod11Check;
import org.hibernate.validator.constraints.Range; 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.annotation.AnnotationUtils;
import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.ClassPathResource;
@@ -77,183 +77,183 @@ import static org.assertj.core.api.Assertions.assertThat;
* *
* @author Andy Wilkinson * @author Andy Wilkinson
*/ */
public class ResourceBundleConstraintDescriptionResolverTests { class ResourceBundleConstraintDescriptionResolverTests {
private final ResourceBundleConstraintDescriptionResolver resolver = new ResourceBundleConstraintDescriptionResolver(); private final ResourceBundleConstraintDescriptionResolver resolver = new ResourceBundleConstraintDescriptionResolver();
@Test @Test
public void defaultMessageAssertFalse() { void defaultMessageAssertFalse() {
assertThat(constraintDescriptionForField("assertFalse")).isEqualTo("Must be false"); assertThat(constraintDescriptionForField("assertFalse")).isEqualTo("Must be false");
} }
@Test @Test
public void defaultMessageAssertTrue() { void defaultMessageAssertTrue() {
assertThat(constraintDescriptionForField("assertTrue")).isEqualTo("Must be true"); assertThat(constraintDescriptionForField("assertTrue")).isEqualTo("Must be true");
} }
@Test @Test
public void defaultMessageCodePointLength() { void defaultMessageCodePointLength() {
assertThat(constraintDescriptionForField("codePointLength")) assertThat(constraintDescriptionForField("codePointLength"))
.isEqualTo("Code point length must be between 2 and 5 inclusive"); .isEqualTo("Code point length must be between 2 and 5 inclusive");
} }
@Test @Test
public void defaultMessageCurrency() { void defaultMessageCurrency() {
assertThat(constraintDescriptionForField("currency")) assertThat(constraintDescriptionForField("currency"))
.isEqualTo("Must be in an accepted currency unit (GBP, USD)"); .isEqualTo("Must be in an accepted currency unit (GBP, USD)");
} }
@Test @Test
public void defaultMessageDecimalMax() { void defaultMessageDecimalMax() {
assertThat(constraintDescriptionForField("decimalMax")).isEqualTo("Must be at most 9.875"); assertThat(constraintDescriptionForField("decimalMax")).isEqualTo("Must be at most 9.875");
} }
@Test @Test
public void defaultMessageDecimalMin() { void defaultMessageDecimalMin() {
assertThat(constraintDescriptionForField("decimalMin")).isEqualTo("Must be at least 1.5"); assertThat(constraintDescriptionForField("decimalMin")).isEqualTo("Must be at least 1.5");
} }
@Test @Test
public void defaultMessageDigits() { void defaultMessageDigits() {
assertThat(constraintDescriptionForField("digits")) assertThat(constraintDescriptionForField("digits"))
.isEqualTo("Must have at most 2 integral digits and 5 fractional digits"); .isEqualTo("Must have at most 2 integral digits and 5 fractional digits");
} }
@Test @Test
public void defaultMessageFuture() { void defaultMessageFuture() {
assertThat(constraintDescriptionForField("future")).isEqualTo("Must be in the future"); assertThat(constraintDescriptionForField("future")).isEqualTo("Must be in the future");
} }
@Test @Test
public void defaultMessageFutureOrPresent() { void defaultMessageFutureOrPresent() {
assertThat(constraintDescriptionForField("futureOrPresent")).isEqualTo("Must be in the future or the present"); assertThat(constraintDescriptionForField("futureOrPresent")).isEqualTo("Must be in the future or the present");
} }
@Test @Test
public void defaultMessageMax() { void defaultMessageMax() {
assertThat(constraintDescriptionForField("max")).isEqualTo("Must be at most 10"); assertThat(constraintDescriptionForField("max")).isEqualTo("Must be at most 10");
} }
@Test @Test
public void defaultMessageMin() { void defaultMessageMin() {
assertThat(constraintDescriptionForField("min")).isEqualTo("Must be at least 10"); assertThat(constraintDescriptionForField("min")).isEqualTo("Must be at least 10");
} }
@Test @Test
public void defaultMessageNotNull() { void defaultMessageNotNull() {
assertThat(constraintDescriptionForField("notNull")).isEqualTo("Must not be null"); assertThat(constraintDescriptionForField("notNull")).isEqualTo("Must not be null");
} }
@Test @Test
public void defaultMessageNull() { void defaultMessageNull() {
assertThat(constraintDescriptionForField("nul")).isEqualTo("Must be null"); assertThat(constraintDescriptionForField("nul")).isEqualTo("Must be null");
} }
@Test @Test
public void defaultMessagePast() { void defaultMessagePast() {
assertThat(constraintDescriptionForField("past")).isEqualTo("Must be in the past"); assertThat(constraintDescriptionForField("past")).isEqualTo("Must be in the past");
} }
@Test @Test
public void defaultMessagePastOrPresent() { void defaultMessagePastOrPresent() {
assertThat(constraintDescriptionForField("pastOrPresent")).isEqualTo("Must be in the past or the present"); assertThat(constraintDescriptionForField("pastOrPresent")).isEqualTo("Must be in the past or the present");
} }
@Test @Test
public void defaultMessagePattern() { void defaultMessagePattern() {
assertThat(constraintDescriptionForField("pattern")) assertThat(constraintDescriptionForField("pattern"))
.isEqualTo("Must match the regular expression `[A-Z][a-z]+`"); .isEqualTo("Must match the regular expression `[A-Z][a-z]+`");
} }
@Test @Test
public void defaultMessageSize() { void defaultMessageSize() {
assertThat(constraintDescriptionForField("size")).isEqualTo("Size must be between 2 and 10 inclusive"); assertThat(constraintDescriptionForField("size")).isEqualTo("Size must be between 2 and 10 inclusive");
} }
@Test @Test
public void defaultMessageCreditCardNumber() { void defaultMessageCreditCardNumber() {
assertThat(constraintDescriptionForField("creditCardNumber")) assertThat(constraintDescriptionForField("creditCardNumber"))
.isEqualTo("Must be a well-formed credit card number"); .isEqualTo("Must be a well-formed credit card number");
} }
@Test @Test
public void defaultMessageEan() { void defaultMessageEan() {
assertThat(constraintDescriptionForField("ean")).isEqualTo("Must be a well-formed EAN13 number"); assertThat(constraintDescriptionForField("ean")).isEqualTo("Must be a well-formed EAN13 number");
} }
@Test @Test
public void defaultMessageEmail() { void defaultMessageEmail() {
assertThat(constraintDescriptionForField("email")).isEqualTo("Must be a well-formed email address"); assertThat(constraintDescriptionForField("email")).isEqualTo("Must be a well-formed email address");
} }
@Test @Test
public void defaultMessageLength() { void defaultMessageLength() {
assertThat(constraintDescriptionForField("length")).isEqualTo("Length must be between 2 and 10 inclusive"); assertThat(constraintDescriptionForField("length")).isEqualTo("Length must be between 2 and 10 inclusive");
} }
@Test @Test
public void defaultMessageLuhnCheck() { void defaultMessageLuhnCheck() {
assertThat(constraintDescriptionForField("luhnCheck")) assertThat(constraintDescriptionForField("luhnCheck"))
.isEqualTo("Must pass the Luhn Modulo 10 checksum algorithm"); .isEqualTo("Must pass the Luhn Modulo 10 checksum algorithm");
} }
@Test @Test
public void defaultMessageMod10Check() { void defaultMessageMod10Check() {
assertThat(constraintDescriptionForField("mod10Check")).isEqualTo("Must pass the Mod10 checksum algorithm"); assertThat(constraintDescriptionForField("mod10Check")).isEqualTo("Must pass the Mod10 checksum algorithm");
} }
@Test @Test
public void defaultMessageMod11Check() { void defaultMessageMod11Check() {
assertThat(constraintDescriptionForField("mod11Check")).isEqualTo("Must pass the Mod11 checksum algorithm"); assertThat(constraintDescriptionForField("mod11Check")).isEqualTo("Must pass the Mod11 checksum algorithm");
} }
@Test @Test
public void defaultMessageNegative() { void defaultMessageNegative() {
assertThat(constraintDescriptionForField("negative")).isEqualTo("Must be negative"); assertThat(constraintDescriptionForField("negative")).isEqualTo("Must be negative");
} }
@Test @Test
public void defaultMessageNegativeOrZero() { void defaultMessageNegativeOrZero() {
assertThat(constraintDescriptionForField("negativeOrZero")).isEqualTo("Must be negative or zero"); assertThat(constraintDescriptionForField("negativeOrZero")).isEqualTo("Must be negative or zero");
} }
@Test @Test
public void defaultMessageNotBlank() { void defaultMessageNotBlank() {
assertThat(constraintDescriptionForField("notBlank")).isEqualTo("Must not be blank"); assertThat(constraintDescriptionForField("notBlank")).isEqualTo("Must not be blank");
} }
@Test @Test
public void defaultMessageNotEmpty() { void defaultMessageNotEmpty() {
assertThat(constraintDescriptionForField("notEmpty")).isEqualTo("Must not be empty"); assertThat(constraintDescriptionForField("notEmpty")).isEqualTo("Must not be empty");
} }
@Test @Test
public void defaultMessageNotEmptyHibernateValidator() { void defaultMessageNotEmptyHibernateValidator() {
assertThat(constraintDescriptionForField("notEmpty")).isEqualTo("Must not be empty"); assertThat(constraintDescriptionForField("notEmpty")).isEqualTo("Must not be empty");
} }
@Test @Test
public void defaultMessagePositive() { void defaultMessagePositive() {
assertThat(constraintDescriptionForField("positive")).isEqualTo("Must be positive"); assertThat(constraintDescriptionForField("positive")).isEqualTo("Must be positive");
} }
@Test @Test
public void defaultMessagePositiveOrZero() { void defaultMessagePositiveOrZero() {
assertThat(constraintDescriptionForField("positiveOrZero")).isEqualTo("Must be positive or zero"); assertThat(constraintDescriptionForField("positiveOrZero")).isEqualTo("Must be positive or zero");
} }
@Test @Test
public void defaultMessageRange() { void defaultMessageRange() {
assertThat(constraintDescriptionForField("range")).isEqualTo("Must be at least 10 and at most 100"); assertThat(constraintDescriptionForField("range")).isEqualTo("Must be at least 10 and at most 100");
} }
@Test @Test
public void defaultMessageUrl() { void defaultMessageUrl() {
assertThat(constraintDescriptionForField("url")).isEqualTo("Must be a well-formed URL"); assertThat(constraintDescriptionForField("url")).isEqualTo("Must be a well-formed URL");
} }
@Test @Test
public void customMessage() { void customMessage() {
Thread.currentThread().setContextClassLoader(new ClassLoader() { Thread.currentThread().setContextClassLoader(new ClassLoader() {
@Override @Override
@@ -279,7 +279,7 @@ public class ResourceBundleConstraintDescriptionResolverTests {
} }
@Test @Test
public void customResourceBundle() { void customResourceBundle() {
ResourceBundle bundle = new ListResourceBundle() { ResourceBundle bundle = new ListResourceBundle() {
@Override @Override
@@ -294,7 +294,7 @@ public class ResourceBundleConstraintDescriptionResolverTests {
} }
@Test @Test
public void allBeanValidationConstraintsAreTested() throws Exception { void allBeanValidationConstraintsAreTested() throws Exception {
PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(); PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
Resource[] resources = resolver.getResources("jakarta/validation/constraints/*.class"); Resource[] resources = resolver.getResources("jakarta/validation/constraints/*.class");
Set<Class<?>> beanValidationConstraints = new HashSet<>(); 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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with 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.assertj.core.description.TextDescription;
import org.hibernate.validator.constraints.CompositionType; import org.hibernate.validator.constraints.CompositionType;
import org.hibernate.validator.constraints.ConstraintComposition; import org.hibernate.validator.constraints.ConstraintComposition;
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.assertThat;
@@ -44,19 +44,19 @@ import static org.assertj.core.api.Assertions.assertThat;
* *
* @author Andy Wilkinson * @author Andy Wilkinson
*/ */
public class ValidatorConstraintResolverTests { class ValidatorConstraintResolverTests {
private final ValidatorConstraintResolver resolver = new ValidatorConstraintResolver(); private final ValidatorConstraintResolver resolver = new ValidatorConstraintResolver();
@Test @Test
public void singleFieldConstraint() { void singleFieldConstraint() {
List<Constraint> constraints = this.resolver.resolveForProperty("single", ConstrainedFields.class); List<Constraint> constraints = this.resolver.resolveForProperty("single", ConstrainedFields.class);
assertThat(constraints).hasSize(1); assertThat(constraints).hasSize(1);
assertThat(constraints.get(0).getName()).isEqualTo(NotNull.class.getName()); assertThat(constraints.get(0).getName()).isEqualTo(NotNull.class.getName());
} }
@Test @Test
public void multipleFieldConstraints() { void multipleFieldConstraints() {
List<Constraint> constraints = this.resolver.resolveForProperty("multiple", ConstrainedFields.class); List<Constraint> constraints = this.resolver.resolveForProperty("multiple", ConstrainedFields.class);
assertThat(constraints).hasSize(2); assertThat(constraints).hasSize(2);
assertThat(constraints.get(0)).is(constraint(NotNull.class)); assertThat(constraints.get(0)).is(constraint(NotNull.class));
@@ -64,13 +64,13 @@ public class ValidatorConstraintResolverTests {
} }
@Test @Test
public void noFieldConstraints() { void noFieldConstraints() {
List<Constraint> constraints = this.resolver.resolveForProperty("none", ConstrainedFields.class); List<Constraint> constraints = this.resolver.resolveForProperty("none", ConstrainedFields.class);
assertThat(constraints).hasSize(0); assertThat(constraints).hasSize(0);
} }
@Test @Test
public void compositeConstraint() { void compositeConstraint() {
List<Constraint> constraints = this.resolver.resolveForProperty("composite", ConstrainedFields.class); List<Constraint> constraints = this.resolver.resolveForProperty("composite", ConstrainedFields.class);
assertThat(constraints).hasSize(1); 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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with 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.Arrays;
import java.util.Collections; import java.util.Collections;
import org.junit.Test; import org.springframework.restdocs.snippet.SnippetException;
import org.springframework.restdocs.testfixtures.jupiter.AssertableSnippets;
import org.springframework.restdocs.AbstractSnippetTests; import org.springframework.restdocs.testfixtures.jupiter.OperationBuilder;
import org.springframework.restdocs.templates.TemplateEngine; import org.springframework.restdocs.testfixtures.jupiter.RenderedSnippetTest;
import org.springframework.restdocs.templates.TemplateFormat; import org.springframework.restdocs.testfixtures.jupiter.SnippetTemplate;
import org.springframework.restdocs.templates.TemplateFormats; import org.springframework.restdocs.testfixtures.jupiter.SnippetTest;
import org.springframework.restdocs.templates.TemplateResourceResolver;
import org.springframework.restdocs.templates.mustache.MustacheTemplateEngine;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.BDDMockito.given; import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.mockito.Mockito.mock;
import static org.springframework.restdocs.cookies.CookieDocumentation.cookieWithName; import static org.springframework.restdocs.cookies.CookieDocumentation.cookieWithName;
import static org.springframework.restdocs.snippet.Attributes.attributes; import static org.springframework.restdocs.snippet.Attributes.attributes;
import static org.springframework.restdocs.snippet.Attributes.key; import static org.springframework.restdocs.snippet.Attributes.key;
@@ -42,138 +39,141 @@ import static org.springframework.restdocs.snippet.Attributes.key;
* @author Clyde Stubbs * @author Clyde Stubbs
* @author Andy Wilkinson * @author Andy Wilkinson
*/ */
public class RequestCookiesSnippetTests extends AbstractSnippetTests { class RequestCookiesSnippetTests {
public RequestCookiesSnippetTests(String name, TemplateFormat templateFormat) { @RenderedSnippetTest
super(name, templateFormat); void requestWithCookies(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
}
@Test
public void requestWithCookies() throws IOException {
new RequestCookiesSnippet( new RequestCookiesSnippet(
Arrays.asList(cookieWithName("tz").description("one"), cookieWithName("logged_in").description("two"))) 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("tz", "Europe%2FLondon")
.cookie("logged_in", "true") .cookie("logged_in", "true")
.build()); .build());
assertThat(this.generatedSnippets.requestCookies()) assertThat(snippets.requestCookies())
.is(tableWithHeader("Name", "Description").row("`tz`", "one").row("`logged_in`", "two")); .isTable((table) -> table.withHeader("Name", "Description").row("`tz`", "one").row("`logged_in`", "two"));
} }
@Test @RenderedSnippetTest
public void ignoredRequestCookie() throws IOException { void ignoredRequestCookie(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
new RequestCookiesSnippet( new RequestCookiesSnippet(
Arrays.asList(cookieWithName("tz").ignored(), cookieWithName("logged_in").description("two"))) 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("tz", "Europe%2FLondon")
.cookie("logged_in", "true") .cookie("logged_in", "true")
.build()); .build());
assertThat(this.generatedSnippets.requestCookies()) assertThat(snippets.requestCookies())
.is(tableWithHeader("Name", "Description").row("`logged_in`", "two")); .isTable((table) -> table.withHeader("Name", "Description").row("`logged_in`", "two"));
} }
@Test @RenderedSnippetTest
public void allUndocumentedCookiesCanBeIgnored() throws IOException { void allUndocumentedCookiesCanBeIgnored(OperationBuilder operationBuilder, AssertableSnippets snippets)
throws IOException {
new RequestCookiesSnippet( new RequestCookiesSnippet(
Arrays.asList(cookieWithName("tz").description("one"), cookieWithName("logged_in").description("two")), Arrays.asList(cookieWithName("tz").description("one"), cookieWithName("logged_in").description("two")),
true) true)
.document(this.operationBuilder.request("http://localhost") .document(operationBuilder.request("http://localhost")
.cookie("tz", "Europe%2FLondon") .cookie("tz", "Europe%2FLondon")
.cookie("logged_in", "true") .cookie("logged_in", "true")
.cookie("user_session", "abcd1234efgh5678") .cookie("user_session", "abcd1234efgh5678")
.build()); .build());
assertThat(this.generatedSnippets.requestCookies()) assertThat(snippets.requestCookies())
.is(tableWithHeader("Name", "Description").row("`tz`", "one").row("`logged_in`", "two")); .isTable((table) -> table.withHeader("Name", "Description").row("`tz`", "one").row("`logged_in`", "two"));
} }
@Test @RenderedSnippetTest
public void missingOptionalCookie() throws IOException { void missingOptionalCookie(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
new RequestCookiesSnippet(Arrays.asList(cookieWithName("tz").description("one").optional(), new RequestCookiesSnippet(Arrays.asList(cookieWithName("tz").description("one").optional(),
cookieWithName("logged_in").description("two"))) cookieWithName("logged_in").description("two")))
.document(this.operationBuilder.request("http://localhost").cookie("logged_in", "true").build()); .document(operationBuilder.request("http://localhost").cookie("logged_in", "true").build());
assertThat(this.generatedSnippets.requestCookies()) assertThat(snippets.requestCookies())
.is(tableWithHeader("Name", "Description").row("`tz`", "one").row("`logged_in`", "two")); .isTable((table) -> table.withHeader("Name", "Description").row("`tz`", "one").row("`logged_in`", "two"));
} }
@Test @RenderedSnippetTest
public void requestCookiesWithCustomAttributes() throws IOException { @SnippetTemplate(snippet = "request-cookies", template = "request-cookies-with-title")
TemplateResourceResolver resolver = mock(TemplateResourceResolver.class); void requestCookiesWithCustomAttributes(OperationBuilder operationBuilder, AssertableSnippets snippets)
given(resolver.resolveTemplateResource("request-cookies")) throws IOException {
.willReturn(snippetResource("request-cookies-with-title"));
new RequestCookiesSnippet(Collections.singletonList(cookieWithName("tz").description("one")), new RequestCookiesSnippet(Collections.singletonList(cookieWithName("tz").description("one")),
attributes(key("title").value("Custom title"))) attributes(key("title").value("Custom title")))
.document(this.operationBuilder .document(operationBuilder.request("http://localhost").cookie("tz", "Europe%2FLondon").build());
.attribute(TemplateEngine.class.getName(), new MustacheTemplateEngine(resolver)) assertThat(snippets.requestCookies()).contains("Custom title");
.request("http://localhost")
.cookie("tz", "Europe%2FLondon")
.build());
assertThat(this.generatedSnippets.requestCookies()).contains("Custom title");
} }
@Test @RenderedSnippetTest
public void requestCookiesWithCustomDescriptorAttributes() throws IOException { @SnippetTemplate(snippet = "request-cookies", template = "request-cookies-with-extra-column")
TemplateResourceResolver resolver = mock(TemplateResourceResolver.class); void requestCookiesWithCustomDescriptorAttributes(OperationBuilder operationBuilder, AssertableSnippets snippets)
given(resolver.resolveTemplateResource("request-cookies")) throws IOException {
.willReturn(snippetResource("request-cookies-with-extra-column"));
new RequestCookiesSnippet( new RequestCookiesSnippet(
Arrays.asList(cookieWithName("tz").description("one").attributes(key("foo").value("alpha")), Arrays.asList(cookieWithName("tz").description("one").attributes(key("foo").value("alpha")),
cookieWithName("logged_in").description("two").attributes(key("foo").value("bravo")))) cookieWithName("logged_in").description("two").attributes(key("foo").value("bravo"))))
.document(this.operationBuilder .document(operationBuilder.request("http://localhost")
.attribute(TemplateEngine.class.getName(), new MustacheTemplateEngine(resolver))
.request("http://localhost")
.cookie("tz", "Europe%2FLondon") .cookie("tz", "Europe%2FLondon")
.cookie("logged_in", "true") .cookie("logged_in", "true")
.build()); .build());
assertThat(this.generatedSnippets.requestCookies()).is(// assertThat(snippets.requestCookies()).isTable((table) -> table.withHeader("Name", "Description", "Foo")
tableWithHeader("Name", "Description", "Foo").row("tz", "one", "alpha") .row("tz", "one", "alpha")
.row("logged_in", "two", "bravo")); .row("logged_in", "two", "bravo"));
} }
@Test @RenderedSnippetTest
public void additionalDescriptors() throws IOException { void additionalDescriptors(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
new RequestCookiesSnippet( new RequestCookiesSnippet(
Arrays.asList(cookieWithName("tz").description("one"), cookieWithName("logged_in").description("two"))) Arrays.asList(cookieWithName("tz").description("one"), cookieWithName("logged_in").description("two")))
.and(cookieWithName("user_session").description("three")) .and(cookieWithName("user_session").description("three"))
.document(this.operationBuilder.request("http://localhost") .document(operationBuilder.request("http://localhost")
.cookie("tz", "Europe%2FLondon") .cookie("tz", "Europe%2FLondon")
.cookie("logged_in", "true") .cookie("logged_in", "true")
.cookie("user_session", "abcd1234efgh5678") .cookie("user_session", "abcd1234efgh5678")
.build()); .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("`logged_in`", "two")
.row("`user_session`", "three")); .row("`user_session`", "three"));
} }
@Test @RenderedSnippetTest
public void additionalDescriptorsWithRelaxedRequestCookies() throws IOException { void additionalDescriptorsWithRelaxedRequestCookies(OperationBuilder operationBuilder, AssertableSnippets snippets)
throws IOException {
new RequestCookiesSnippet( new RequestCookiesSnippet(
Arrays.asList(cookieWithName("tz").description("one"), cookieWithName("logged_in").description("two")), Arrays.asList(cookieWithName("tz").description("one"), cookieWithName("logged_in").description("two")),
true) true)
.and(cookieWithName("user_session").description("three")) .and(cookieWithName("user_session").description("three"))
.document(this.operationBuilder.request("http://localhost") .document(operationBuilder.request("http://localhost")
.cookie("tz", "Europe%2FLondon") .cookie("tz", "Europe%2FLondon")
.cookie("logged_in", "true") .cookie("logged_in", "true")
.cookie("user_session", "abcd1234efgh5678") .cookie("user_session", "abcd1234efgh5678")
.cookie("color_theme", "light") .cookie("color_theme", "light")
.build()); .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("`logged_in`", "two")
.row("`user_session`", "three")); .row("`user_session`", "three"));
} }
@Test @RenderedSnippetTest
public void tableCellContentIsEscapedWhenNecessary() throws IOException { void tableCellContentIsEscapedWhenNecessary(OperationBuilder operationBuilder, AssertableSnippets snippets)
throws IOException {
new RequestCookiesSnippet(Collections.singletonList(cookieWithName("Foo|Bar").description("one|two"))) new RequestCookiesSnippet(Collections.singletonList(cookieWithName("Foo|Bar").description("one|two")))
.document(this.operationBuilder.request("http://localhost").cookie("Foo|Bar", "baz").build()); .document(operationBuilder.request("http://localhost").cookie("Foo|Bar", "baz").build());
assertThat(this.generatedSnippets.requestCookies()).is(tableWithHeader("Name", "Description") assertThat(snippets.requestCookies())
.row(escapeIfNecessary("`Foo|Bar`"), escapeIfNecessary("one|two"))); .isTable((table) -> table.withHeader("Name", "Description").row("`Foo|Bar`", "one|two"));
} }
private String escapeIfNecessary(String input) { @SnippetTest
if (this.templateFormat.getId().equals(TemplateFormats.markdown().getId())) { void missingRequestCookie(OperationBuilder operationBuilder) {
return input; assertThatExceptionOfType(SnippetException.class)
} .isThrownBy(() -> new RequestCookiesSnippet(
return input.replace("|", "\\|"); 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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with 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.Arrays;
import java.util.Collections; import java.util.Collections;
import org.junit.Test; import org.springframework.restdocs.testfixtures.jupiter.AssertableSnippets;
import org.springframework.restdocs.testfixtures.jupiter.OperationBuilder;
import org.springframework.restdocs.AbstractSnippetTests; import org.springframework.restdocs.testfixtures.jupiter.RenderedSnippetTest;
import org.springframework.restdocs.templates.TemplateEngine; import org.springframework.restdocs.testfixtures.jupiter.SnippetTemplate;
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 static org.assertj.core.api.Assertions.assertThat; 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.cookies.CookieDocumentation.cookieWithName;
import static org.springframework.restdocs.snippet.Attributes.attributes; import static org.springframework.restdocs.snippet.Attributes.attributes;
import static org.springframework.restdocs.snippet.Attributes.key; import static org.springframework.restdocs.snippet.Attributes.key;
@@ -42,141 +36,127 @@ import static org.springframework.restdocs.snippet.Attributes.key;
* @author Clyde Stubbs * @author Clyde Stubbs
* @author Andy Wilkinson * @author Andy Wilkinson
*/ */
public class ResponseCookiesSnippetTests extends AbstractSnippetTests { class ResponseCookiesSnippetTests {
public ResponseCookiesSnippetTests(String name, TemplateFormat templateFormat) { @RenderedSnippetTest
super(name, templateFormat); void responseWithCookies(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
}
@Test
public void responseWithCookies() throws IOException {
new ResponseCookiesSnippet(Arrays.asList(cookieWithName("has_recent_activity").description("one"), new ResponseCookiesSnippet(Arrays.asList(cookieWithName("has_recent_activity").description("one"),
cookieWithName("user_session").description("two"))) cookieWithName("user_session").description("two")))
.document(this.operationBuilder.response() .document(operationBuilder.response()
.cookie("has_recent_activity", "true") .cookie("has_recent_activity", "true")
.cookie("user_session", "1234abcd5678efgh") .cookie("user_session", "1234abcd5678efgh")
.build()); .build());
assertThat(this.generatedSnippets.responseCookies()) assertThat(snippets.responseCookies()).isTable((table) -> table.withHeader("Name", "Description")
.is(tableWithHeader("Name", "Description").row("`has_recent_activity`", "one") .row("`has_recent_activity`", "one")
.row("`user_session`", "two")); .row("`user_session`", "two"));
} }
@Test @RenderedSnippetTest
public void ignoredResponseCookie() throws IOException { void ignoredResponseCookie(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
new ResponseCookiesSnippet(Arrays.asList(cookieWithName("has_recent_activity").ignored(), new ResponseCookiesSnippet(Arrays.asList(cookieWithName("has_recent_activity").ignored(),
cookieWithName("user_session").description("two"))) cookieWithName("user_session").description("two")))
.document(this.operationBuilder.response() .document(operationBuilder.response()
.cookie("has_recent_activity", "true") .cookie("has_recent_activity", "true")
.cookie("user_session", "1234abcd5678efgh") .cookie("user_session", "1234abcd5678efgh")
.build()); .build());
assertThat(this.generatedSnippets.responseCookies()) assertThat(snippets.responseCookies())
.is(tableWithHeader("Name", "Description").row("`user_session`", "two")); .isTable((table) -> table.withHeader("Name", "Description").row("`user_session`", "two"));
} }
@Test @RenderedSnippetTest
public void allUndocumentedResponseCookiesCanBeIgnored() throws IOException { void allUndocumentedResponseCookiesCanBeIgnored(OperationBuilder operationBuilder, AssertableSnippets snippets)
throws IOException {
new ResponseCookiesSnippet(Arrays.asList(cookieWithName("has_recent_activity").description("one"), new ResponseCookiesSnippet(Arrays.asList(cookieWithName("has_recent_activity").description("one"),
cookieWithName("user_session").description("two")), true) cookieWithName("user_session").description("two")), true)
.document(this.operationBuilder.response() .document(operationBuilder.response()
.cookie("has_recent_activity", "true") .cookie("has_recent_activity", "true")
.cookie("user_session", "1234abcd5678efgh") .cookie("user_session", "1234abcd5678efgh")
.cookie("some_cookie", "value") .cookie("some_cookie", "value")
.build()); .build());
assertThat(this.generatedSnippets.responseCookies()) assertThat(snippets.responseCookies()).isTable((table) -> table.withHeader("Name", "Description")
.is(tableWithHeader("Name", "Description").row("`has_recent_activity`", "one") .row("`has_recent_activity`", "one")
.row("`user_session`", "two")); .row("`user_session`", "two"));
} }
@Test @RenderedSnippetTest
public void missingOptionalResponseCookie() throws IOException { void missingOptionalResponseCookie(OperationBuilder operationBuilder, AssertableSnippets snippets)
throws IOException {
new ResponseCookiesSnippet(Arrays.asList(cookieWithName("has_recent_activity").description("one").optional(), new ResponseCookiesSnippet(Arrays.asList(cookieWithName("has_recent_activity").description("one").optional(),
cookieWithName("user_session").description("two"))) cookieWithName("user_session").description("two")))
.document(this.operationBuilder.response().cookie("user_session", "1234abcd5678efgh").build()); .document(operationBuilder.response().cookie("user_session", "1234abcd5678efgh").build());
assertThat(this.generatedSnippets.responseCookies()) assertThat(snippets.responseCookies()).isTable((table) -> table.withHeader("Name", "Description")
.is(tableWithHeader("Name", "Description").row("`has_recent_activity`", "one") .row("`has_recent_activity`", "one")
.row("`user_session`", "two")); .row("`user_session`", "two"));
} }
@Test @RenderedSnippetTest
public void responseCookiesWithCustomAttributes() throws IOException { @SnippetTemplate(snippet = "response-cookies", template = "response-cookies-with-title")
TemplateResourceResolver resolver = mock(TemplateResourceResolver.class); void responseCookiesWithCustomAttributes(OperationBuilder operationBuilder, AssertableSnippets snippets)
given(resolver.resolveTemplateResource("response-cookies")) throws IOException {
.willReturn(snippetResource("response-cookies-with-title"));
new ResponseCookiesSnippet(Collections.singletonList(cookieWithName("has_recent_activity").description("one")), new ResponseCookiesSnippet(Collections.singletonList(cookieWithName("has_recent_activity").description("one")),
attributes(key("title").value("Custom title"))) attributes(key("title").value("Custom title")))
.document(this.operationBuilder .document(operationBuilder.response().cookie("has_recent_activity", "true").build());
.attribute(TemplateEngine.class.getName(), new MustacheTemplateEngine(resolver)) assertThat(snippets.responseCookies()).contains("Custom title");
.response()
.cookie("has_recent_activity", "true")
.build());
assertThat(this.generatedSnippets.responseCookies()).contains("Custom title");
} }
@Test @RenderedSnippetTest
public void responseCookiesWithCustomDescriptorAttributes() throws IOException { @SnippetTemplate(snippet = "response-cookies", template = "response-cookies-with-extra-column")
TemplateResourceResolver resolver = mock(TemplateResourceResolver.class); void responseCookiesWithCustomDescriptorAttributes(OperationBuilder operationBuilder, AssertableSnippets snippets)
given(resolver.resolveTemplateResource("response-cookies")) throws IOException {
.willReturn(snippetResource("response-cookies-with-extra-column"));
new ResponseCookiesSnippet(Arrays.asList( new ResponseCookiesSnippet(Arrays.asList(
cookieWithName("has_recent_activity").description("one").attributes(key("foo").value("alpha")), cookieWithName("has_recent_activity").description("one").attributes(key("foo").value("alpha")),
cookieWithName("user_session").description("two").attributes(key("foo").value("bravo")), cookieWithName("user_session").description("two").attributes(key("foo").value("bravo")),
cookieWithName("color_theme").description("three").attributes(key("foo").value("charlie")))) cookieWithName("color_theme").description("three").attributes(key("foo").value("charlie"))))
.document(this.operationBuilder .document(operationBuilder.response()
.attribute(TemplateEngine.class.getName(), new MustacheTemplateEngine(resolver))
.response()
.cookie("has_recent_activity", "true") .cookie("has_recent_activity", "true")
.cookie("user_session", "1234abcd5678efgh") .cookie("user_session", "1234abcd5678efgh")
.cookie("color_theme", "high_contrast") .cookie("color_theme", "high_contrast")
.build()); .build());
assertThat(this.generatedSnippets.responseCookies()) assertThat(snippets.responseCookies()).isTable((table) -> table.withHeader("Name", "Description", "Foo")
.is(tableWithHeader("Name", "Description", "Foo").row("has_recent_activity", "one", "alpha") .row("has_recent_activity", "one", "alpha")
.row("user_session", "two", "bravo") .row("user_session", "two", "bravo")
.row("color_theme", "three", "charlie")); .row("color_theme", "three", "charlie"));
} }
@Test @RenderedSnippetTest
public void additionalDescriptors() throws IOException { void additionalDescriptors(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
CookieDocumentation CookieDocumentation
.responseCookies(cookieWithName("has_recent_activity").description("one"), .responseCookies(cookieWithName("has_recent_activity").description("one"),
cookieWithName("user_session").description("two")) cookieWithName("user_session").description("two"))
.and(cookieWithName("color_theme").description("three")) .and(cookieWithName("color_theme").description("three"))
.document(this.operationBuilder.response() .document(operationBuilder.response()
.cookie("has_recent_activity", "true") .cookie("has_recent_activity", "true")
.cookie("user_session", "1234abcd5678efgh") .cookie("user_session", "1234abcd5678efgh")
.cookie("color_theme", "light") .cookie("color_theme", "light")
.build()); .build());
assertThat(this.generatedSnippets.responseCookies()) assertThat(snippets.responseCookies()).isTable((table) -> table.withHeader("Name", "Description")
.is(tableWithHeader("Name", "Description").row("`has_recent_activity`", "one") .row("`has_recent_activity`", "one")
.row("`user_session`", "two") .row("`user_session`", "two")
.row("`color_theme`", "three")); .row("`color_theme`", "three"));
} }
@Test @RenderedSnippetTest
public void additionalDescriptorsWithRelaxedResponseCookies() throws IOException { void additionalDescriptorsWithRelaxedResponseCookies(OperationBuilder operationBuilder, AssertableSnippets snippets)
throws IOException {
CookieDocumentation.relaxedResponseCookies(cookieWithName("has_recent_activity").description("one")) CookieDocumentation.relaxedResponseCookies(cookieWithName("has_recent_activity").description("one"))
.and(cookieWithName("color_theme").description("two")) .and(cookieWithName("color_theme").description("two"))
.document(this.operationBuilder.response() .document(operationBuilder.response()
.cookie("has_recent_activity", "true") .cookie("has_recent_activity", "true")
.cookie("user_session", "1234abcd5678efgh") .cookie("user_session", "1234abcd5678efgh")
.cookie("color_theme", "light") .cookie("color_theme", "light")
.build()); .build());
assertThat(this.generatedSnippets.responseCookies()) assertThat(snippets.responseCookies()).isTable((table) -> table.withHeader("Name", "Description")
.is(tableWithHeader("Name", "Description").row("`has_recent_activity`", "one").row("`color_theme`", "two")); .row("`has_recent_activity`", "one")
.row("`color_theme`", "two"));
} }
@Test @RenderedSnippetTest
public void tableCellContentIsEscapedWhenNecessary() throws IOException { void tableCellContentIsEscapedWhenNecessary(OperationBuilder operationBuilder, AssertableSnippets snippets)
throws IOException {
new ResponseCookiesSnippet(Collections.singletonList(cookieWithName("Foo|Bar").description("one|two"))) new ResponseCookiesSnippet(Collections.singletonList(cookieWithName("Foo|Bar").description("one|two")))
.document(this.operationBuilder.response().cookie("Foo|Bar", "baz").build()); .document(operationBuilder.response().cookie("Foo|Bar", "baz").build());
assertThat(this.generatedSnippets.responseCookies()).is(tableWithHeader("Name", "Description") assertThat(snippets.responseCookies())
.row(escapeIfNecessary("`Foo|Bar`"), escapeIfNecessary("one|two"))); .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("|", "\\|");
} }
} }

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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with 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.io.IOException;
import java.util.Arrays; import java.util.Arrays;
import org.junit.Test; import org.springframework.restdocs.snippet.SnippetException;
import org.springframework.restdocs.testfixtures.jupiter.AssertableSnippets;
import org.springframework.restdocs.AbstractSnippetTests; import org.springframework.restdocs.testfixtures.jupiter.OperationBuilder;
import org.springframework.restdocs.templates.TemplateEngine; import org.springframework.restdocs.testfixtures.jupiter.RenderedSnippetTest;
import org.springframework.restdocs.templates.TemplateFormat; import org.springframework.restdocs.testfixtures.jupiter.SnippetTemplate;
import org.springframework.restdocs.templates.TemplateFormats; import org.springframework.restdocs.testfixtures.jupiter.SnippetTest;
import org.springframework.restdocs.templates.TemplateResourceResolver;
import org.springframework.restdocs.templates.mustache.MustacheTemplateEngine;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.BDDMockito.given; import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.mockito.Mockito.mock;
import static org.springframework.restdocs.headers.HeaderDocumentation.headerWithName; import static org.springframework.restdocs.headers.HeaderDocumentation.headerWithName;
import static org.springframework.restdocs.snippet.Attributes.attributes; import static org.springframework.restdocs.snippet.Attributes.attributes;
import static org.springframework.restdocs.snippet.Attributes.key; import static org.springframework.restdocs.snippet.Attributes.key;
@@ -41,19 +38,15 @@ import static org.springframework.restdocs.snippet.Attributes.key;
* @author Andreas Evers * @author Andreas Evers
* @author Andy Wilkinson * @author Andy Wilkinson
*/ */
public class RequestHeadersSnippetTests extends AbstractSnippetTests { class RequestHeadersSnippetTests {
public RequestHeadersSnippetTests(String name, TemplateFormat templateFormat) { @RenderedSnippetTest
super(name, templateFormat); void requestWithHeaders(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
}
@Test
public void requestWithHeaders() throws IOException {
new RequestHeadersSnippet(Arrays.asList(headerWithName("X-Test").description("one"), new RequestHeadersSnippet(Arrays.asList(headerWithName("X-Test").description("one"),
headerWithName("Accept").description("two"), headerWithName("Accept-Encoding").description("three"), headerWithName("Accept").description("two"), headerWithName("Accept-Encoding").description("three"),
headerWithName("Accept-Language").description("four"), headerWithName("Accept-Language").description("four"),
headerWithName("Cache-Control").description("five"), headerWithName("Connection").description("six"))) 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("X-Test", "test")
.header("Accept", "*/*") .header("Accept", "*/*")
.header("Accept-Encoding", "gzip, deflate") .header("Accept-Encoding", "gzip, deflate")
@@ -61,79 +54,69 @@ public class RequestHeadersSnippetTests extends AbstractSnippetTests {
.header("Cache-Control", "max-age=0") .header("Cache-Control", "max-age=0")
.header("Connection", "keep-alive") .header("Connection", "keep-alive")
.build()); .build());
assertThat(this.generatedSnippets.requestHeaders()) assertThat(snippets.requestHeaders()).isTable((table) -> table.withHeader("Name", "Description")
.is(tableWithHeader("Name", "Description").row("`X-Test`", "one") .row("`X-Test`", "one")
.row("`Accept`", "two") .row("`Accept`", "two")
.row("`Accept-Encoding`", "three") .row("`Accept-Encoding`", "three")
.row("`Accept-Language`", "four") .row("`Accept-Language`", "four")
.row("`Cache-Control`", "five") .row("`Cache-Control`", "five")
.row("`Connection`", "six")); .row("`Connection`", "six"));
} }
@Test @RenderedSnippetTest
public void caseInsensitiveRequestHeaders() throws IOException { void caseInsensitiveRequestHeaders(OperationBuilder operationBuilder, AssertableSnippets snippets)
throws IOException {
new RequestHeadersSnippet(Arrays.asList(headerWithName("X-Test").description("one"))) new RequestHeadersSnippet(Arrays.asList(headerWithName("X-Test").description("one")))
.document(this.operationBuilder.request("/").header("X-test", "test").build()); .document(operationBuilder.request("/").header("X-test", "test").build());
assertThat(this.generatedSnippets.requestHeaders()) assertThat(snippets.requestHeaders())
.is(tableWithHeader("Name", "Description").row("`X-Test`", "one")); .isTable((table) -> table.withHeader("Name", "Description").row("`X-Test`", "one"));
} }
@Test @RenderedSnippetTest
public void undocumentedRequestHeader() throws IOException { void undocumentedRequestHeader(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
new RequestHeadersSnippet(Arrays.asList(headerWithName("X-Test").description("one"))) new RequestHeadersSnippet(Arrays.asList(headerWithName("X-Test").description("one"))).document(
.document(this.operationBuilder.request("http://localhost") operationBuilder.request("http://localhost").header("X-Test", "test").header("Accept", "*/*").build());
.header("X-Test", "test") assertThat(snippets.requestHeaders())
.header("Accept", "*/*") .isTable((table) -> table.withHeader("Name", "Description").row("`X-Test`", "one"));
.build());
assertThat(this.generatedSnippets.requestHeaders())
.is(tableWithHeader("Name", "Description").row("`X-Test`", "one"));
} }
@Test @RenderedSnippetTest
public void requestHeadersWithCustomAttributes() throws IOException { @SnippetTemplate(snippet = "request-headers", template = "request-headers-with-title")
TemplateResourceResolver resolver = mock(TemplateResourceResolver.class); void requestHeadersWithCustomAttributes(OperationBuilder operationBuilder, AssertableSnippets snippets)
given(resolver.resolveTemplateResource("request-headers")) throws IOException {
.willReturn(snippetResource("request-headers-with-title"));
new RequestHeadersSnippet(Arrays.asList(headerWithName("X-Test").description("one")), new RequestHeadersSnippet(Arrays.asList(headerWithName("X-Test").description("one")),
attributes(key("title").value("Custom title"))) attributes(key("title").value("Custom title")))
.document(this.operationBuilder .document(operationBuilder.request("http://localhost").header("X-Test", "test").build());
.attribute(TemplateEngine.class.getName(), new MustacheTemplateEngine(resolver)) assertThat(snippets.requestHeaders()).contains("Custom title");
.request("http://localhost")
.header("X-Test", "test")
.build());
assertThat(this.generatedSnippets.requestHeaders()).contains("Custom title");
} }
@Test @RenderedSnippetTest
public void requestHeadersWithCustomDescriptorAttributes() throws IOException { @SnippetTemplate(snippet = "request-headers", template = "request-headers-with-extra-column")
TemplateResourceResolver resolver = mock(TemplateResourceResolver.class); void requestHeadersWithCustomDescriptorAttributes(OperationBuilder operationBuilder, AssertableSnippets snippets)
given(resolver.resolveTemplateResource("request-headers")) throws IOException {
.willReturn(snippetResource("request-headers-with-extra-column"));
new RequestHeadersSnippet( new RequestHeadersSnippet(
Arrays.asList(headerWithName("X-Test").description("one").attributes(key("foo").value("alpha")), Arrays.asList(headerWithName("X-Test").description("one").attributes(key("foo").value("alpha")),
headerWithName("Accept-Encoding").description("two").attributes(key("foo").value("bravo")), headerWithName("Accept-Encoding").description("two").attributes(key("foo").value("bravo")),
headerWithName("Accept").description("three").attributes(key("foo").value("charlie")))) headerWithName("Accept").description("three").attributes(key("foo").value("charlie"))))
.document(this.operationBuilder .document(operationBuilder.request("http://localhost")
.attribute(TemplateEngine.class.getName(), new MustacheTemplateEngine(resolver))
.request("http://localhost")
.header("X-Test", "test") .header("X-Test", "test")
.header("Accept-Encoding", "gzip, deflate") .header("Accept-Encoding", "gzip, deflate")
.header("Accept", "*/*") .header("Accept", "*/*")
.build()); .build());
assertThat(this.generatedSnippets.requestHeaders()).is(// assertThat(snippets.requestHeaders()).isTable((table) -> table.withHeader("Name", "Description", "Foo")
tableWithHeader("Name", "Description", "Foo").row("X-Test", "one", "alpha") .row("X-Test", "one", "alpha")
.row("Accept-Encoding", "two", "bravo") .row("Accept-Encoding", "two", "bravo")
.row("Accept", "three", "charlie")); .row("Accept", "three", "charlie"));
} }
@Test @RenderedSnippetTest
public void additionalDescriptors() throws IOException { void additionalDescriptors(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
HeaderDocumentation HeaderDocumentation
.requestHeaders(headerWithName("X-Test").description("one"), headerWithName("Accept").description("two"), .requestHeaders(headerWithName("X-Test").description("one"), headerWithName("Accept").description("two"),
headerWithName("Accept-Encoding").description("three"), headerWithName("Accept-Encoding").description("three"),
headerWithName("Accept-Language").description("four")) headerWithName("Accept-Language").description("four"))
.and(headerWithName("Cache-Control").description("five"), headerWithName("Connection").description("six")) .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("X-Test", "test")
.header("Accept", "*/*") .header("Accept", "*/*")
.header("Accept-Encoding", "gzip, deflate") .header("Accept-Encoding", "gzip, deflate")
@@ -141,28 +124,39 @@ public class RequestHeadersSnippetTests extends AbstractSnippetTests {
.header("Cache-Control", "max-age=0") .header("Cache-Control", "max-age=0")
.header("Connection", "keep-alive") .header("Connection", "keep-alive")
.build()); .build());
assertThat(this.generatedSnippets.requestHeaders()) assertThat(snippets.requestHeaders()).isTable((table) -> table.withHeader("Name", "Description")
.is(tableWithHeader("Name", "Description").row("`X-Test`", "one") .row("`X-Test`", "one")
.row("`Accept`", "two") .row("`Accept`", "two")
.row("`Accept-Encoding`", "three") .row("`Accept-Encoding`", "three")
.row("`Accept-Language`", "four") .row("`Accept-Language`", "four")
.row("`Cache-Control`", "five") .row("`Cache-Control`", "five")
.row("`Connection`", "six")); .row("`Connection`", "six"));
} }
@Test @RenderedSnippetTest
public void tableCellContentIsEscapedWhenNecessary() throws IOException { void tableCellContentIsEscapedWhenNecessary(OperationBuilder operationBuilder, AssertableSnippets snippets)
throws IOException {
new RequestHeadersSnippet(Arrays.asList(headerWithName("Foo|Bar").description("one|two"))) new RequestHeadersSnippet(Arrays.asList(headerWithName("Foo|Bar").description("one|two")))
.document(this.operationBuilder.request("http://localhost").header("Foo|Bar", "baz").build()); .document(operationBuilder.request("http://localhost").header("Foo|Bar", "baz").build());
assertThat(this.generatedSnippets.requestHeaders()).is(tableWithHeader("Name", "Description") assertThat(snippets.requestHeaders())
.row(escapeIfNecessary("`Foo|Bar`"), escapeIfNecessary("one|two"))); .isTable((table) -> table.withHeader("Name", "Description").row("`Foo|Bar`", "one|two"));
} }
private String escapeIfNecessary(String input) { @SnippetTest
if (this.templateFormat.getId().equals(TemplateFormats.markdown().getId())) { void missingRequestHeader(OperationBuilder operationBuilder) {
return input; assertThatExceptionOfType(SnippetException.class)
} .isThrownBy(() -> new RequestHeadersSnippet(Arrays.asList(headerWithName("Accept").description("one")))
return input.replace("|", "\\|"); .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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with 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.io.IOException;
import java.util.Arrays; import java.util.Arrays;
import org.junit.Test; import org.springframework.restdocs.snippet.SnippetException;
import org.springframework.restdocs.testfixtures.jupiter.AssertableSnippets;
import org.springframework.restdocs.AbstractSnippetTests; import org.springframework.restdocs.testfixtures.jupiter.OperationBuilder;
import org.springframework.restdocs.templates.TemplateEngine; import org.springframework.restdocs.testfixtures.jupiter.RenderedSnippetTest;
import org.springframework.restdocs.templates.TemplateFormat; import org.springframework.restdocs.testfixtures.jupiter.SnippetTemplate;
import org.springframework.restdocs.templates.TemplateFormats; import org.springframework.restdocs.testfixtures.jupiter.SnippetTest;
import org.springframework.restdocs.templates.TemplateResourceResolver;
import org.springframework.restdocs.templates.mustache.MustacheTemplateEngine;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.BDDMockito.given; import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.mockito.Mockito.mock;
import static org.springframework.restdocs.headers.HeaderDocumentation.headerWithName; import static org.springframework.restdocs.headers.HeaderDocumentation.headerWithName;
import static org.springframework.restdocs.snippet.Attributes.attributes; import static org.springframework.restdocs.snippet.Attributes.attributes;
import static org.springframework.restdocs.snippet.Attributes.key; import static org.springframework.restdocs.snippet.Attributes.key;
@@ -41,119 +38,120 @@ import static org.springframework.restdocs.snippet.Attributes.key;
* @author Andreas Evers * @author Andreas Evers
* @author Andy Wilkinson * @author Andy Wilkinson
*/ */
public class ResponseHeadersSnippetTests extends AbstractSnippetTests { class ResponseHeadersSnippetTests {
public ResponseHeadersSnippetTests(String name, TemplateFormat templateFormat) { @RenderedSnippetTest
super(name, templateFormat); void responseWithHeaders(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
}
@Test
public void responseWithHeaders() throws IOException {
new ResponseHeadersSnippet(Arrays.asList(headerWithName("X-Test").description("one"), new ResponseHeadersSnippet(Arrays.asList(headerWithName("X-Test").description("one"),
headerWithName("Content-Type").description("two"), headerWithName("Etag").description("three"), headerWithName("Content-Type").description("two"), headerWithName("Etag").description("three"),
headerWithName("Cache-Control").description("five"), headerWithName("Vary").description("six"))) headerWithName("Cache-Control").description("five"), headerWithName("Vary").description("six")))
.document(this.operationBuilder.response() .document(operationBuilder.response()
.header("X-Test", "test") .header("X-Test", "test")
.header("Content-Type", "application/json") .header("Content-Type", "application/json")
.header("Etag", "lskjadldj3ii32l2ij23") .header("Etag", "lskjadldj3ii32l2ij23")
.header("Cache-Control", "max-age=0") .header("Cache-Control", "max-age=0")
.header("Vary", "User-Agent") .header("Vary", "User-Agent")
.build()); .build());
assertThat(this.generatedSnippets.responseHeaders()) assertThat(snippets.responseHeaders()).isTable((table) -> table.withHeader("Name", "Description")
.is(tableWithHeader("Name", "Description").row("`X-Test`", "one") .row("`X-Test`", "one")
.row("`Content-Type`", "two") .row("`Content-Type`", "two")
.row("`Etag`", "three") .row("`Etag`", "three")
.row("`Cache-Control`", "five") .row("`Cache-Control`", "five")
.row("`Vary`", "six")); .row("`Vary`", "six"));
} }
@Test @RenderedSnippetTest
public void caseInsensitiveResponseHeaders() throws IOException { void caseInsensitiveResponseHeaders(OperationBuilder operationBuilder, AssertableSnippets snippets)
throws IOException {
new ResponseHeadersSnippet(Arrays.asList(headerWithName("X-Test").description("one"))) new ResponseHeadersSnippet(Arrays.asList(headerWithName("X-Test").description("one")))
.document(this.operationBuilder.response().header("X-test", "test").build()); .document(operationBuilder.response().header("X-test", "test").build());
assertThat(this.generatedSnippets.responseHeaders()) assertThat(snippets.responseHeaders())
.is(tableWithHeader("Name", "Description").row("`X-Test`", "one")); .isTable((table) -> table.withHeader("Name", "Description").row("`X-Test`", "one"));
} }
@Test @RenderedSnippetTest
public void undocumentedResponseHeader() throws IOException { void undocumentedResponseHeader(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
new ResponseHeadersSnippet(Arrays.asList(headerWithName("X-Test").description("one"))) new ResponseHeadersSnippet(Arrays.asList(headerWithName("X-Test").description("one")))
.document(this.operationBuilder.response().header("X-Test", "test").header("Content-Type", "*/*").build()); .document(operationBuilder.response().header("X-Test", "test").header("Content-Type", "*/*").build());
assertThat(this.generatedSnippets.responseHeaders()) assertThat(snippets.responseHeaders())
.is(tableWithHeader("Name", "Description").row("`X-Test`", "one")); .isTable((table) -> table.withHeader("Name", "Description").row("`X-Test`", "one"));
} }
@Test @RenderedSnippetTest
public void responseHeadersWithCustomAttributes() throws IOException { @SnippetTemplate(snippet = "response-headers", template = "response-headers-with-title")
TemplateResourceResolver resolver = mock(TemplateResourceResolver.class); void responseHeadersWithCustomAttributes(OperationBuilder operationBuilder, AssertableSnippets snippets)
given(resolver.resolveTemplateResource("response-headers")) throws IOException {
.willReturn(snippetResource("response-headers-with-title"));
new ResponseHeadersSnippet(Arrays.asList(headerWithName("X-Test").description("one")), new ResponseHeadersSnippet(Arrays.asList(headerWithName("X-Test").description("one")),
attributes(key("title").value("Custom title"))) attributes(key("title").value("Custom title")))
.document(this.operationBuilder .document(operationBuilder.response().header("X-Test", "test").build());
.attribute(TemplateEngine.class.getName(), new MustacheTemplateEngine(resolver)) assertThat(snippets.responseHeaders()).contains("Custom title");
.response()
.header("X-Test", "test")
.build());
assertThat(this.generatedSnippets.responseHeaders()).contains("Custom title");
} }
@Test @RenderedSnippetTest
public void responseHeadersWithCustomDescriptorAttributes() throws IOException { @SnippetTemplate(snippet = "response-headers", template = "response-headers-with-extra-column")
TemplateResourceResolver resolver = mock(TemplateResourceResolver.class); void responseHeadersWithCustomDescriptorAttributes(OperationBuilder operationBuilder, AssertableSnippets snippets)
given(resolver.resolveTemplateResource("response-headers")) throws IOException {
.willReturn(snippetResource("response-headers-with-extra-column"));
new ResponseHeadersSnippet( new ResponseHeadersSnippet(
Arrays.asList(headerWithName("X-Test").description("one").attributes(key("foo").value("alpha")), Arrays.asList(headerWithName("X-Test").description("one").attributes(key("foo").value("alpha")),
headerWithName("Content-Type").description("two").attributes(key("foo").value("bravo")), headerWithName("Content-Type").description("two").attributes(key("foo").value("bravo")),
headerWithName("Etag").description("three").attributes(key("foo").value("charlie")))) headerWithName("Etag").description("three").attributes(key("foo").value("charlie"))))
.document(this.operationBuilder .document(operationBuilder.response()
.attribute(TemplateEngine.class.getName(), new MustacheTemplateEngine(resolver))
.response()
.header("X-Test", "test") .header("X-Test", "test")
.header("Content-Type", "application/json") .header("Content-Type", "application/json")
.header("Etag", "lskjadldj3ii32l2ij23") .header("Etag", "lskjadldj3ii32l2ij23")
.build()); .build());
assertThat(this.generatedSnippets.responseHeaders()) assertThat(snippets.responseHeaders()).isTable((table) -> table.withHeader("Name", "Description", "Foo")
.is(tableWithHeader("Name", "Description", "Foo").row("X-Test", "one", "alpha") .row("X-Test", "one", "alpha")
.row("Content-Type", "two", "bravo") .row("Content-Type", "two", "bravo")
.row("Etag", "three", "charlie")); .row("Etag", "three", "charlie"));
} }
@Test @RenderedSnippetTest
public void additionalDescriptors() throws IOException { void additionalDescriptors(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
HeaderDocumentation HeaderDocumentation
.responseHeaders(headerWithName("X-Test").description("one"), .responseHeaders(headerWithName("X-Test").description("one"),
headerWithName("Content-Type").description("two"), headerWithName("Etag").description("three")) headerWithName("Content-Type").description("two"), headerWithName("Etag").description("three"))
.and(headerWithName("Cache-Control").description("five"), headerWithName("Vary").description("six")) .and(headerWithName("Cache-Control").description("five"), headerWithName("Vary").description("six"))
.document(this.operationBuilder.response() .document(operationBuilder.response()
.header("X-Test", "test") .header("X-Test", "test")
.header("Content-Type", "application/json") .header("Content-Type", "application/json")
.header("Etag", "lskjadldj3ii32l2ij23") .header("Etag", "lskjadldj3ii32l2ij23")
.header("Cache-Control", "max-age=0") .header("Cache-Control", "max-age=0")
.header("Vary", "User-Agent") .header("Vary", "User-Agent")
.build()); .build());
assertThat(this.generatedSnippets.responseHeaders()) assertThat(snippets.responseHeaders()).isTable((table) -> table.withHeader("Name", "Description")
.is(tableWithHeader("Name", "Description").row("`X-Test`", "one") .row("`X-Test`", "one")
.row("`Content-Type`", "two") .row("`Content-Type`", "two")
.row("`Etag`", "three") .row("`Etag`", "three")
.row("`Cache-Control`", "five") .row("`Cache-Control`", "five")
.row("`Vary`", "six")); .row("`Vary`", "six"));
} }
@Test @RenderedSnippetTest
public void tableCellContentIsEscapedWhenNecessary() throws IOException { void tableCellContentIsEscapedWhenNecessary(OperationBuilder operationBuilder, AssertableSnippets snippets)
throws IOException {
new ResponseHeadersSnippet(Arrays.asList(headerWithName("Foo|Bar").description("one|two"))) new ResponseHeadersSnippet(Arrays.asList(headerWithName("Foo|Bar").description("one|two")))
.document(this.operationBuilder.response().header("Foo|Bar", "baz").build()); .document(operationBuilder.response().header("Foo|Bar", "baz").build());
assertThat(this.generatedSnippets.responseHeaders()).is(tableWithHeader("Name", "Description") assertThat(snippets.responseHeaders())
.row(escapeIfNecessary("`Foo|Bar`"), escapeIfNecessary("one|two"))); .isTable((table) -> table.withHeader("Name", "Description").row("`Foo|Bar`", "one|two"));
} }
private String escapeIfNecessary(String input) { @SnippetTest
if (this.templateFormat.getId().equals(TemplateFormats.markdown().getId())) { void missingResponseHeader(OperationBuilder operationBuilder) {
return input; assertThatExceptionOfType(SnippetException.class)
} .isThrownBy(
return input.replace("|", "\\|"); () -> 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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with 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 java.io.IOException;
import org.junit.Test;
import org.springframework.http.HttpHeaders; import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.restdocs.AbstractSnippetTests; import org.springframework.restdocs.testfixtures.jupiter.AssertableSnippets;
import org.springframework.restdocs.templates.TemplateEngine; import org.springframework.restdocs.testfixtures.jupiter.OperationBuilder;
import org.springframework.restdocs.templates.TemplateFormat; import org.springframework.restdocs.testfixtures.jupiter.RenderedSnippetTest;
import org.springframework.restdocs.templates.TemplateResourceResolver; import org.springframework.restdocs.testfixtures.jupiter.SnippetTemplate;
import org.springframework.restdocs.templates.mustache.MustacheTemplateEngine;
import org.springframework.web.bind.annotation.RequestMethod;
import static org.assertj.core.api.Assertions.assertThat; 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.attributes;
import static org.springframework.restdocs.snippet.Attributes.key; import static org.springframework.restdocs.snippet.Attributes.key;
@@ -41,162 +35,160 @@ import static org.springframework.restdocs.snippet.Attributes.key;
* @author Andy Wilkinson * @author Andy Wilkinson
* @author Jonathan Pearlin * @author Jonathan Pearlin
*/ */
public class HttpRequestSnippetTests extends AbstractSnippetTests { class HttpRequestSnippetTests {
private static final String BOUNDARY = "6o2knFse3p53ty9dmcQvWAIx1zInP11uCfbm"; private static final String BOUNDARY = "6o2knFse3p53ty9dmcQvWAIx1zInP11uCfbm";
public HttpRequestSnippetTests(String name, TemplateFormat templateFormat) { @RenderedSnippetTest
super(name, templateFormat); void getRequest(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
}
@Test
public void getRequest() throws IOException {
new HttpRequestSnippet() new HttpRequestSnippet()
.document(this.operationBuilder.request("http://localhost/foo").header("Alpha", "a").build()); .document(operationBuilder.request("http://localhost/foo").header("Alpha", "a").build());
assertThat(this.generatedSnippets.httpRequest()) assertThat(snippets.httpRequest())
.is(httpRequest(RequestMethod.GET, "/foo").header("Alpha", "a").header(HttpHeaders.HOST, "localhost")); .isHttpRequest((request) -> request.get("/foo").header("Alpha", "a").header(HttpHeaders.HOST, "localhost"));
} }
@Test @RenderedSnippetTest
public void getRequestWithQueryParameters() throws IOException { void getRequestWithQueryParameters(OperationBuilder operationBuilder, AssertableSnippets snippets)
throws IOException {
new HttpRequestSnippet() new HttpRequestSnippet()
.document(this.operationBuilder.request("http://localhost/foo?b=bravo").header("Alpha", "a").build()); .document(operationBuilder.request("http://localhost/foo?b=bravo").header("Alpha", "a").build());
assertThat(this.generatedSnippets.httpRequest()) assertThat(snippets.httpRequest()).isHttpRequest(
.is(httpRequest(RequestMethod.GET, "/foo?b=bravo").header("Alpha", "a") (request) -> request.get("/foo?b=bravo").header("Alpha", "a").header(HttpHeaders.HOST, "localhost"));
.header(HttpHeaders.HOST, "localhost"));
} }
@Test @RenderedSnippetTest
public void getRequestWithPort() throws IOException { void getRequestWithPort(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
new HttpRequestSnippet() new HttpRequestSnippet()
.document(this.operationBuilder.request("http://localhost:8080/foo").header("Alpha", "a").build()); .document(operationBuilder.request("http://localhost:8080/foo").header("Alpha", "a").build());
assertThat(this.generatedSnippets.httpRequest()) assertThat(snippets.httpRequest()).isHttpRequest(
.is(httpRequest(RequestMethod.GET, "/foo").header("Alpha", "a").header(HttpHeaders.HOST, "localhost:8080")); (request) -> request.get("/foo").header("Alpha", "a").header(HttpHeaders.HOST, "localhost:8080"));
} }
@Test @RenderedSnippetTest
public void getRequestWithCookies() throws IOException { void getRequestWithCookies(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
new HttpRequestSnippet().document(this.operationBuilder.request("http://localhost/foo") new HttpRequestSnippet().document(operationBuilder.request("http://localhost/foo")
.cookie("name1", "value1") .cookie("name1", "value1")
.cookie("name2", "value2") .cookie("name2", "value2")
.build()); .build());
assertThat(this.generatedSnippets.httpRequest()) assertThat(snippets.httpRequest()).isHttpRequest((request) -> request.get("/foo")
.is(httpRequest(RequestMethod.GET, "/foo").header(HttpHeaders.HOST, "localhost") .header(HttpHeaders.HOST, "localhost")
.header(HttpHeaders.COOKIE, "name1=value1; name2=value2")); .header(HttpHeaders.COOKIE, "name1=value1; name2=value2"));
} }
@Test @RenderedSnippetTest
public void getRequestWithQueryString() throws IOException { void getRequestWithQueryString(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
new HttpRequestSnippet().document(this.operationBuilder.request("http://localhost/foo?bar=baz").build()); new HttpRequestSnippet().document(operationBuilder.request("http://localhost/foo?bar=baz").build());
assertThat(this.generatedSnippets.httpRequest()) assertThat(snippets.httpRequest())
.is(httpRequest(RequestMethod.GET, "/foo?bar=baz").header(HttpHeaders.HOST, "localhost")); .isHttpRequest((request) -> request.get("/foo?bar=baz").header(HttpHeaders.HOST, "localhost"));
} }
@Test @RenderedSnippetTest
public void getRequestWithQueryStringWithNoValue() throws IOException { void getRequestWithQueryStringWithNoValue(OperationBuilder operationBuilder, AssertableSnippets snippets)
new HttpRequestSnippet().document(this.operationBuilder.request("http://localhost/foo?bar").build()); throws IOException {
assertThat(this.generatedSnippets.httpRequest()) new HttpRequestSnippet().document(operationBuilder.request("http://localhost/foo?bar").build());
.is(httpRequest(RequestMethod.GET, "/foo?bar").header(HttpHeaders.HOST, "localhost")); assertThat(snippets.httpRequest())
.isHttpRequest((request) -> request.get("/foo?bar").header(HttpHeaders.HOST, "localhost"));
} }
@Test @RenderedSnippetTest
public void postRequestWithContent() throws IOException { void postRequestWithContent(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
String content = "Hello, world"; String content = "Hello, world";
new HttpRequestSnippet() new HttpRequestSnippet()
.document(this.operationBuilder.request("http://localhost/foo").method("POST").content(content).build()); .document(operationBuilder.request("http://localhost/foo").method("POST").content(content).build());
assertThat(this.generatedSnippets.httpRequest()) assertThat(snippets.httpRequest()).isHttpRequest((request) -> request.post("/foo")
.is(httpRequest(RequestMethod.POST, "/foo").header(HttpHeaders.HOST, "localhost") .header(HttpHeaders.HOST, "localhost")
.content(content) .content(content)
.header(HttpHeaders.CONTENT_LENGTH, content.getBytes().length)); .header(HttpHeaders.CONTENT_LENGTH, content.getBytes().length));
} }
@Test @RenderedSnippetTest
public void postRequestWithContentAndQueryParameters() throws IOException { void postRequestWithContentAndQueryParameters(OperationBuilder operationBuilder, AssertableSnippets snippets)
throws IOException {
String content = "Hello, world"; String content = "Hello, world";
new HttpRequestSnippet().document( new HttpRequestSnippet()
this.operationBuilder.request("http://localhost/foo?a=alpha").method("POST").content(content).build()); .document(operationBuilder.request("http://localhost/foo?a=alpha").method("POST").content(content).build());
assertThat(this.generatedSnippets.httpRequest()) assertThat(snippets.httpRequest()).isHttpRequest((request) -> request.post("/foo?a=alpha")
.is(httpRequest(RequestMethod.POST, "/foo?a=alpha").header(HttpHeaders.HOST, "localhost") .header(HttpHeaders.HOST, "localhost")
.content(content) .content(content)
.header(HttpHeaders.CONTENT_LENGTH, content.getBytes().length)); .header(HttpHeaders.CONTENT_LENGTH, content.getBytes().length));
} }
@Test @RenderedSnippetTest
public void postRequestWithCharset() throws IOException { void postRequestWithCharset(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
String japaneseContent = "\u30b3\u30f3\u30c6\u30f3\u30c4"; String japaneseContent = "\u30b3\u30f3\u30c6\u30f3\u30c4";
byte[] contentBytes = japaneseContent.getBytes("UTF-8"); 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") .method("POST")
.header("Content-Type", "text/plain;charset=UTF-8") .header("Content-Type", "text/plain;charset=UTF-8")
.content(contentBytes) .content(contentBytes)
.build()); .build());
assertThat(this.generatedSnippets.httpRequest()) assertThat(snippets.httpRequest()).isHttpRequest((request) -> request.post("/foo")
.is(httpRequest(RequestMethod.POST, "/foo").header("Content-Type", "text/plain;charset=UTF-8") .header("Content-Type", "text/plain;charset=UTF-8")
.header(HttpHeaders.HOST, "localhost") .header(HttpHeaders.HOST, "localhost")
.header(HttpHeaders.CONTENT_LENGTH, contentBytes.length) .header(HttpHeaders.CONTENT_LENGTH, contentBytes.length)
.content(japaneseContent)); .content(japaneseContent));
} }
@Test @RenderedSnippetTest
public void putRequestWithContent() throws IOException { void putRequestWithContent(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
String content = "Hello, world"; String content = "Hello, world";
new HttpRequestSnippet() new HttpRequestSnippet()
.document(this.operationBuilder.request("http://localhost/foo").method("PUT").content(content).build()); .document(operationBuilder.request("http://localhost/foo").method("PUT").content(content).build());
assertThat(this.generatedSnippets.httpRequest()) assertThat(snippets.httpRequest()).isHttpRequest((request) -> request.put("/foo")
.is(httpRequest(RequestMethod.PUT, "/foo").header(HttpHeaders.HOST, "localhost") .header(HttpHeaders.HOST, "localhost")
.content(content) .content(content)
.header(HttpHeaders.CONTENT_LENGTH, content.getBytes().length)); .header(HttpHeaders.CONTENT_LENGTH, content.getBytes().length));
} }
@Test @RenderedSnippetTest
public void multipartPost() throws IOException { void multipartPost(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
new HttpRequestSnippet().document(this.operationBuilder.request("http://localhost/upload") new HttpRequestSnippet().document(operationBuilder.request("http://localhost/upload")
.method("POST") .method("POST")
.header(HttpHeaders.CONTENT_TYPE, MediaType.MULTIPART_FORM_DATA_VALUE) .header(HttpHeaders.CONTENT_TYPE, MediaType.MULTIPART_FORM_DATA_VALUE)
.part("image", "<< data >>".getBytes()) .part("image", "<< data >>".getBytes())
.build()); .build());
String expectedContent = createPart( String expectedContent = createPart(
String.format("Content-Disposition: " + "form-data; " + "name=image%n%n<< data >>")); 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("Content-Type", "multipart/form-data; boundary=" + BOUNDARY)
.header(HttpHeaders.HOST, "localhost") .header(HttpHeaders.HOST, "localhost")
.content(expectedContent)); .content(expectedContent));
} }
@Test @RenderedSnippetTest
public void multipartPut() throws IOException { void multipartPut(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
new HttpRequestSnippet().document(this.operationBuilder.request("http://localhost/upload") new HttpRequestSnippet().document(operationBuilder.request("http://localhost/upload")
.method("PUT") .method("PUT")
.header(HttpHeaders.CONTENT_TYPE, MediaType.MULTIPART_FORM_DATA_VALUE) .header(HttpHeaders.CONTENT_TYPE, MediaType.MULTIPART_FORM_DATA_VALUE)
.part("image", "<< data >>".getBytes()) .part("image", "<< data >>".getBytes())
.build()); .build());
String expectedContent = createPart( String expectedContent = createPart(
String.format("Content-Disposition: " + "form-data; " + "name=image%n%n<< data >>")); 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("Content-Type", "multipart/form-data; boundary=" + BOUNDARY)
.header(HttpHeaders.HOST, "localhost") .header(HttpHeaders.HOST, "localhost")
.content(expectedContent)); .content(expectedContent));
} }
@Test @RenderedSnippetTest
public void multipartPatch() throws IOException { void multipartPatch(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
new HttpRequestSnippet().document(this.operationBuilder.request("http://localhost/upload") new HttpRequestSnippet().document(operationBuilder.request("http://localhost/upload")
.method("PATCH") .method("PATCH")
.header(HttpHeaders.CONTENT_TYPE, MediaType.MULTIPART_FORM_DATA_VALUE) .header(HttpHeaders.CONTENT_TYPE, MediaType.MULTIPART_FORM_DATA_VALUE)
.part("image", "<< data >>".getBytes()) .part("image", "<< data >>".getBytes())
.build()); .build());
String expectedContent = createPart( String expectedContent = createPart(
String.format("Content-Disposition: " + "form-data; " + "name=image%n%n<< data >>")); 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("Content-Type", "multipart/form-data; boundary=" + BOUNDARY)
.header(HttpHeaders.HOST, "localhost") .header(HttpHeaders.HOST, "localhost")
.content(expectedContent)); .content(expectedContent));
} }
@Test @RenderedSnippetTest
public void multipartPostWithFilename() throws IOException { void multipartPostWithFilename(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
new HttpRequestSnippet().document(this.operationBuilder.request("http://localhost/upload") new HttpRequestSnippet().document(operationBuilder.request("http://localhost/upload")
.method("POST") .method("POST")
.header(HttpHeaders.CONTENT_TYPE, MediaType.MULTIPART_FORM_DATA_VALUE) .header(HttpHeaders.CONTENT_TYPE, MediaType.MULTIPART_FORM_DATA_VALUE)
.part("image", "<< data >>".getBytes()) .part("image", "<< data >>".getBytes())
@@ -204,15 +196,16 @@ public class HttpRequestSnippetTests extends AbstractSnippetTests {
.build()); .build());
String expectedContent = createPart(String String expectedContent = createPart(String
.format("Content-Disposition: " + "form-data; " + "name=image; filename=image.png%n%n<< data >>")); .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("Content-Type", "multipart/form-data; boundary=" + BOUNDARY)
.header(HttpHeaders.HOST, "localhost") .header(HttpHeaders.HOST, "localhost")
.content(expectedContent)); .content(expectedContent));
} }
@Test @RenderedSnippetTest
public void multipartPostWithContentType() throws IOException { void multipartPostWithContentType(OperationBuilder operationBuilder, AssertableSnippets snippets)
new HttpRequestSnippet().document(this.operationBuilder.request("http://localhost/upload") throws IOException {
new HttpRequestSnippet().document(operationBuilder.request("http://localhost/upload")
.method("POST") .method("POST")
.header(HttpHeaders.CONTENT_TYPE, MediaType.MULTIPART_FORM_DATA_VALUE) .header(HttpHeaders.CONTENT_TYPE, MediaType.MULTIPART_FORM_DATA_VALUE)
.part("image", "<< data >>".getBytes()) .part("image", "<< data >>".getBytes())
@@ -220,38 +213,35 @@ public class HttpRequestSnippetTests extends AbstractSnippetTests {
.build()); .build());
String expectedContent = createPart(String String expectedContent = createPart(String
.format("Content-Disposition: form-data; name=image%nContent-Type: " + "image/png%n%n<< data >>")); .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("Content-Type", "multipart/form-data; boundary=" + BOUNDARY)
.header(HttpHeaders.HOST, "localhost") .header(HttpHeaders.HOST, "localhost")
.content(expectedContent)); .content(expectedContent));
} }
@Test @RenderedSnippetTest
public void getRequestWithCustomHost() throws IOException { void getRequestWithCustomHost(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
new HttpRequestSnippet().document(this.operationBuilder.request("http://localhost/foo") new HttpRequestSnippet().document(
.header(HttpHeaders.HOST, "api.example.com") operationBuilder.request("http://localhost/foo").header(HttpHeaders.HOST, "api.example.com").build());
.build()); assertThat(snippets.httpRequest())
assertThat(this.generatedSnippets.httpRequest()) .isHttpRequest((request) -> request.get("/foo").header(HttpHeaders.HOST, "api.example.com"));
.is(httpRequest(RequestMethod.GET, "/foo").header(HttpHeaders.HOST, "api.example.com"));
} }
@Test @RenderedSnippetTest
public void requestWithCustomSnippetAttributes() throws IOException { @SnippetTemplate(snippet = "http-request", template = "http-request-with-title")
TemplateResourceResolver resolver = mock(TemplateResourceResolver.class); void requestWithCustomSnippetAttributes(OperationBuilder operationBuilder, AssertableSnippets snippets)
given(resolver.resolveTemplateResource("http-request")).willReturn(snippetResource("http-request-with-title")); throws IOException {
new HttpRequestSnippet(attributes(key("title").value("Title for the request"))).document( new HttpRequestSnippet(attributes(key("title").value("Title for the request")))
this.operationBuilder.attribute(TemplateEngine.class.getName(), new MustacheTemplateEngine(resolver)) .document(operationBuilder.request("http://localhost/foo").build());
.request("http://localhost/foo") assertThat(snippets.httpRequest()).contains("Title for the request");
.build());
assertThat(this.generatedSnippets.httpRequest()).contains("Title for the request");
} }
@Test @RenderedSnippetTest
public void deleteWithQueryString() throws IOException { void deleteWithQueryString(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
new HttpRequestSnippet() new HttpRequestSnippet()
.document(this.operationBuilder.request("http://localhost/foo?a=alpha&b=bravo").method("DELETE").build()); .document(operationBuilder.request("http://localhost/foo?a=alpha&b=bravo").method("DELETE").build());
assertThat(this.generatedSnippets.httpRequest()) assertThat(snippets.httpRequest())
.is(httpRequest(RequestMethod.DELETE, "/foo?a=alpha&b=bravo").header("Host", "localhost")); .isHttpRequest((request) -> request.delete("/foo?a=alpha&b=bravo").header("Host", "localhost"));
} }
private String createPart(String content) { 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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with 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 java.io.IOException;
import org.junit.Test;
import org.springframework.http.HttpHeaders; import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.http.HttpStatusCode; import org.springframework.http.HttpStatusCode;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.restdocs.AbstractSnippetTests; import org.springframework.restdocs.testfixtures.jupiter.AssertableSnippets;
import org.springframework.restdocs.templates.TemplateEngine; import org.springframework.restdocs.testfixtures.jupiter.OperationBuilder;
import org.springframework.restdocs.templates.TemplateFormat; import org.springframework.restdocs.testfixtures.jupiter.RenderedSnippetTest;
import org.springframework.restdocs.templates.TemplateResourceResolver; import org.springframework.restdocs.testfixtures.jupiter.SnippetTemplate;
import org.springframework.restdocs.templates.mustache.MustacheTemplateEngine;
import static org.assertj.core.api.Assertions.assertThat; 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.attributes;
import static org.springframework.restdocs.snippet.Attributes.key; import static org.springframework.restdocs.snippet.Attributes.key;
@@ -42,72 +37,66 @@ import static org.springframework.restdocs.snippet.Attributes.key;
* @author Andy Wilkinson * @author Andy Wilkinson
* @author Jonathan Pearlin * @author Jonathan Pearlin
*/ */
public class HttpResponseSnippetTests extends AbstractSnippetTests { class HttpResponseSnippetTests {
public HttpResponseSnippetTests(String name, TemplateFormat templateFormat) { @RenderedSnippetTest
super(name, templateFormat); void basicResponse(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
new HttpResponseSnippet().document(operationBuilder.build());
assertThat(snippets.httpResponse()).isHttpResponse((response) -> response.ok());
} }
@Test @RenderedSnippetTest
public void basicResponse() throws IOException { void nonOkResponse(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
new HttpResponseSnippet().document(this.operationBuilder.build()); new HttpResponseSnippet().document(operationBuilder.response().status(HttpStatus.BAD_REQUEST).build());
assertThat(this.generatedSnippets.httpResponse()).is(httpResponse(HttpStatus.OK)); assertThat(snippets.httpResponse()).isHttpResponse((response) -> response.badRequest());
} }
@Test @RenderedSnippetTest
public void nonOkResponse() throws IOException { void responseWithHeaders(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
new HttpResponseSnippet().document(this.operationBuilder.response().status(HttpStatus.BAD_REQUEST).build()); new HttpResponseSnippet().document(operationBuilder.response()
assertThat(this.generatedSnippets.httpResponse()).is(httpResponse(HttpStatus.BAD_REQUEST));
}
@Test
public void responseWithHeaders() throws IOException {
new HttpResponseSnippet().document(this.operationBuilder.response()
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
.header("a", "alpha") .header("a", "alpha")
.build()); .build());
assertThat(this.generatedSnippets.httpResponse()) assertThat(snippets.httpResponse()).isHttpResponse(
.is(httpResponse(HttpStatus.OK).header("Content-Type", "application/json").header("a", "alpha")); (response) -> response.ok().header("Content-Type", "application/json").header("a", "alpha"));
} }
@Test @RenderedSnippetTest
public void responseWithContent() throws IOException { void responseWithContent(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
String content = "content"; String content = "content";
new HttpResponseSnippet().document(this.operationBuilder.response().content(content).build()); new HttpResponseSnippet().document(operationBuilder.response().content(content).build());
assertThat(this.generatedSnippets.httpResponse()).is(httpResponse(HttpStatus.OK).content(content) assertThat(snippets.httpResponse()).isHttpResponse((response) -> response.ok()
.content(content)
.header(HttpHeaders.CONTENT_LENGTH, content.getBytes().length)); .header(HttpHeaders.CONTENT_LENGTH, content.getBytes().length));
} }
@Test @RenderedSnippetTest
public void responseWithCharset() throws IOException { void responseWithCharset(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
String japaneseContent = "\u30b3\u30f3\u30c6\u30f3\u30c4"; String japaneseContent = "\u30b3\u30f3\u30c6\u30f3\u30c4";
byte[] contentBytes = japaneseContent.getBytes("UTF-8"); 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") .header("Content-Type", "text/plain;charset=UTF-8")
.content(contentBytes) .content(contentBytes)
.build()); .build());
assertThat(this.generatedSnippets.httpResponse()) assertThat(snippets.httpResponse()).isHttpResponse((response) -> response.ok()
.is(httpResponse(HttpStatus.OK).header("Content-Type", "text/plain;charset=UTF-8") .header("Content-Type", "text/plain;charset=UTF-8")
.content(japaneseContent) .content(japaneseContent)
.header(HttpHeaders.CONTENT_LENGTH, contentBytes.length)); .header(HttpHeaders.CONTENT_LENGTH, contentBytes.length));
} }
@Test @RenderedSnippetTest
public void responseWithCustomSnippetAttributes() throws IOException { @SnippetTemplate(snippet = "http-response", template = "http-response-with-title")
TemplateResourceResolver resolver = mock(TemplateResourceResolver.class); void responseWithCustomSnippetAttributes(OperationBuilder operationBuilder, AssertableSnippets snippets)
given(resolver.resolveTemplateResource("http-response")) throws IOException {
.willReturn(snippetResource("http-response-with-title")); new HttpResponseSnippet(attributes(key("title").value("Title for the response")))
new HttpResponseSnippet(attributes(key("title").value("Title for the response"))).document( .document(operationBuilder.build());
this.operationBuilder.attribute(TemplateEngine.class.getName(), new MustacheTemplateEngine(resolver)) assertThat(snippets.httpResponse()).contains("Title for the response");
.build());
assertThat(this.generatedSnippets.httpResponse()).contains("Title for the response");
} }
@Test @RenderedSnippetTest
public void responseWithCustomStatus() throws IOException { void responseWithCustomStatus(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
new HttpResponseSnippet() new HttpResponseSnippet().document(operationBuilder.response().status(HttpStatusCode.valueOf(215)).build());
.document(this.operationBuilder.response().status(HttpStatusCode.valueOf(215)).build()); assertThat(snippets.httpResponse()).isHttpResponse(((response) -> response.status(215)));
assertThat(this.generatedSnippets.httpResponse()).is(httpResponse(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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with 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.HashMap;
import java.util.Map; import java.util.Map;
import org.junit.Test; import org.junit.jupiter.api.Test;
import org.springframework.http.HttpHeaders; import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
@@ -37,18 +37,18 @@ import static org.mockito.Mockito.verify;
* *
* @author Andy Wilkinson * @author Andy Wilkinson
*/ */
public class ContentTypeLinkExtractorTests { class ContentTypeLinkExtractorTests {
private final OperationResponseFactory responseFactory = new OperationResponseFactory(); private final OperationResponseFactory responseFactory = new OperationResponseFactory();
@Test @Test
public void extractionFailsWithNullContentType() { void extractionFailsWithNullContentType() {
assertThatIllegalStateException().isThrownBy(() -> new ContentTypeLinkExtractor() assertThatIllegalStateException().isThrownBy(() -> new ContentTypeLinkExtractor()
.extractLinks(this.responseFactory.create(HttpStatus.OK, new HttpHeaders(), null))); .extractLinks(this.responseFactory.create(HttpStatus.OK, new HttpHeaders(), null)));
} }
@Test @Test
public void extractorCalledWithMatchingContextType() throws IOException { void extractorCalledWithMatchingContextType() throws IOException {
Map<MediaType, LinkExtractor> extractors = new HashMap<>(); Map<MediaType, LinkExtractor> extractors = new HashMap<>();
LinkExtractor extractor = mock(LinkExtractor.class); LinkExtractor extractor = mock(LinkExtractor.class);
extractors.put(MediaType.APPLICATION_JSON, extractor); extractors.put(MediaType.APPLICATION_JSON, extractor);
@@ -60,7 +60,7 @@ public class ContentTypeLinkExtractorTests {
} }
@Test @Test
public void extractorCalledWithCompatibleContextType() throws IOException { void extractorCalledWithCompatibleContextType() throws IOException {
Map<MediaType, LinkExtractor> extractors = new HashMap<>(); Map<MediaType, LinkExtractor> extractors = new HashMap<>();
LinkExtractor extractor = mock(LinkExtractor.class); LinkExtractor extractor = mock(LinkExtractor.class);
extractors.put(MediaType.APPLICATION_JSON, extractor); 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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with 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.List;
import java.util.Map; import java.util.Map;
import org.junit.Test; import org.junit.jupiter.api.Test;
import org.junit.runner.RunWith; import org.junit.jupiter.params.ParameterizedClass;
import org.junit.runners.Parameterized; import org.junit.jupiter.params.provider.MethodSource;
import org.junit.runners.Parameterized.Parameters;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.restdocs.operation.OperationResponse; import org.springframework.restdocs.operation.OperationResponse;
@@ -39,13 +38,13 @@ import org.springframework.util.MultiValueMap;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
/** /**
* Parameterized tests for {@link HalLinkExtractor} and {@link AtomLinkExtractor} with * Tests for {@link HalLinkExtractor} and {@link AtomLinkExtractor} with various payloads.
* various payloads.
* *
* @author Andy Wilkinson * @author Andy Wilkinson
*/ */
@RunWith(Parameterized.class) @ParameterizedClass(name = "{1}")
public class LinkExtractorsPayloadTests { @MethodSource("parameters")
class LinkExtractorsPayloadTests {
private final OperationResponseFactory responseFactory = new OperationResponseFactory(); private final OperationResponseFactory responseFactory = new OperationResponseFactory();
@@ -53,25 +52,24 @@ public class LinkExtractorsPayloadTests {
private final String linkType; private final String linkType;
@Parameters(name = "{1}") static Collection<Object[]> parameters() {
public static Collection<Object[]> data() {
return Arrays.asList(new Object[] { new HalLinkExtractor(), "hal" }, return Arrays.asList(new Object[] { new HalLinkExtractor(), "hal" },
new Object[] { new AtomLinkExtractor(), "atom" }); new Object[] { new AtomLinkExtractor(), "atom" });
} }
public LinkExtractorsPayloadTests(LinkExtractor linkExtractor, String linkType) { LinkExtractorsPayloadTests(LinkExtractor linkExtractor, String linkType) {
this.linkExtractor = linkExtractor; this.linkExtractor = linkExtractor;
this.linkType = linkType; this.linkType = linkType;
} }
@Test @Test
public void singleLink() throws IOException { void singleLink() throws IOException {
Map<String, List<Link>> links = this.linkExtractor.extractLinks(createResponse("single-link")); Map<String, List<Link>> links = this.linkExtractor.extractLinks(createResponse("single-link"));
assertLinks(Arrays.asList(new Link("alpha", "https://alpha.example.com", "Alpha")), links); assertLinks(Arrays.asList(new Link("alpha", "https://alpha.example.com", "Alpha")), links);
} }
@Test @Test
public void multipleLinksWithDifferentRels() throws IOException { void multipleLinksWithDifferentRels() throws IOException {
Map<String, List<Link>> links = this.linkExtractor Map<String, List<Link>> links = this.linkExtractor
.extractLinks(createResponse("multiple-links-different-rels")); .extractLinks(createResponse("multiple-links-different-rels"));
assertLinks(Arrays.asList(new Link("alpha", "https://alpha.example.com", "Alpha"), assertLinks(Arrays.asList(new Link("alpha", "https://alpha.example.com", "Alpha"),
@@ -79,20 +77,20 @@ public class LinkExtractorsPayloadTests {
} }
@Test @Test
public void multipleLinksWithSameRels() throws IOException { void multipleLinksWithSameRels() throws IOException {
Map<String, List<Link>> links = this.linkExtractor.extractLinks(createResponse("multiple-links-same-rels")); 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"), assertLinks(Arrays.asList(new Link("alpha", "https://alpha.example.com/one", "Alpha one"),
new Link("alpha", "https://alpha.example.com/two")), links); new Link("alpha", "https://alpha.example.com/two")), links);
} }
@Test @Test
public void noLinks() throws IOException { void noLinks() throws IOException {
Map<String, List<Link>> links = this.linkExtractor.extractLinks(createResponse("no-links")); Map<String, List<Link>> links = this.linkExtractor.extractLinks(createResponse("no-links"));
assertLinks(Collections.<Link>emptyList(), links); assertLinks(Collections.<Link>emptyList(), links);
} }
@Test @Test
public void linksInTheWrongFormat() throws IOException { void linksInTheWrongFormat() throws IOException {
Map<String, List<Link>> links = this.linkExtractor.extractLinks(createResponse("wrong-format")); Map<String, List<Link>> links = this.linkExtractor.extractLinks(createResponse("wrong-format"));
assertLinks(Collections.<Link>emptyList(), links); 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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with 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.io.IOException;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections;
import org.junit.Test; import org.springframework.restdocs.snippet.SnippetException;
import org.springframework.restdocs.testfixtures.jupiter.AssertableSnippets;
import org.springframework.restdocs.AbstractSnippetTests; import org.springframework.restdocs.testfixtures.jupiter.OperationBuilder;
import org.springframework.restdocs.templates.TemplateEngine; import org.springframework.restdocs.testfixtures.jupiter.RenderedSnippetTest;
import org.springframework.restdocs.templates.TemplateFormat; import org.springframework.restdocs.testfixtures.jupiter.SnippetTemplate;
import org.springframework.restdocs.templates.TemplateFormats; import org.springframework.restdocs.testfixtures.jupiter.SnippetTest;
import org.springframework.restdocs.templates.TemplateResourceResolver;
import org.springframework.restdocs.templates.mustache.MustacheTemplateEngine;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.BDDMockito.given; import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.mockito.Mockito.mock;
import static org.springframework.restdocs.snippet.Attributes.attributes; import static org.springframework.restdocs.snippet.Attributes.attributes;
import static org.springframework.restdocs.snippet.Attributes.key; import static org.springframework.restdocs.snippet.Attributes.key;
@@ -39,115 +37,145 @@ import static org.springframework.restdocs.snippet.Attributes.key;
* *
* @author Andy Wilkinson * @author Andy Wilkinson
*/ */
public class LinksSnippetTests extends AbstractSnippetTests { class LinksSnippetTests {
public LinksSnippetTests(String name, TemplateFormat templateFormat) { @RenderedSnippetTest
super(name, templateFormat); void ignoredLink(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
}
@Test
public void ignoredLink() throws IOException {
new LinksSnippet(new StubLinkExtractor().withLinks(new Link("a", "alpha"), new Link("b", "bravo")), 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"))) Arrays.asList(new LinkDescriptor("a").ignored(), new LinkDescriptor("b").description("Link b")))
.document(this.operationBuilder.build()); .document(operationBuilder.build());
assertThat(this.generatedSnippets.links()).is(tableWithHeader("Relation", "Description").row("`b`", "Link b")); assertThat(snippets.links())
.isTable((table) -> table.withHeader("Relation", "Description").row("`b`", "Link b"));
} }
@Test @RenderedSnippetTest
public void allUndocumentedLinksCanBeIgnored() throws IOException { void allUndocumentedLinksCanBeIgnored(OperationBuilder operationBuilder, AssertableSnippets snippets)
throws IOException {
new LinksSnippet(new StubLinkExtractor().withLinks(new Link("a", "alpha"), new Link("b", "bravo")), new LinksSnippet(new StubLinkExtractor().withLinks(new Link("a", "alpha"), new Link("b", "bravo")),
Arrays.asList(new LinkDescriptor("b").description("Link b")), true) Arrays.asList(new LinkDescriptor("b").description("Link b")), true)
.document(this.operationBuilder.build()); .document(operationBuilder.build());
assertThat(this.generatedSnippets.links()).is(tableWithHeader("Relation", "Description").row("`b`", "Link b")); assertThat(snippets.links())
.isTable((table) -> table.withHeader("Relation", "Description").row("`b`", "Link b"));
} }
@Test @RenderedSnippetTest
public void presentOptionalLink() throws IOException { void presentOptionalLink(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
new LinksSnippet(new StubLinkExtractor().withLinks(new Link("foo", "blah")), new LinksSnippet(new StubLinkExtractor().withLinks(new Link("foo", "blah")),
Arrays.asList(new LinkDescriptor("foo").description("bar").optional())) Arrays.asList(new LinkDescriptor("foo").description("bar").optional()))
.document(this.operationBuilder.build()); .document(operationBuilder.build());
assertThat(this.generatedSnippets.links()).is(tableWithHeader("Relation", "Description").row("`foo`", "bar")); assertThat(snippets.links())
.isTable((table) -> table.withHeader("Relation", "Description").row("`foo`", "bar"));
} }
@Test @RenderedSnippetTest
public void missingOptionalLink() throws IOException { void missingOptionalLink(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
new LinksSnippet(new StubLinkExtractor(), new LinksSnippet(new StubLinkExtractor(),
Arrays.asList(new LinkDescriptor("foo").description("bar").optional())) Arrays.asList(new LinkDescriptor("foo").description("bar").optional()))
.document(this.operationBuilder.build()); .document(operationBuilder.build());
assertThat(this.generatedSnippets.links()).is(tableWithHeader("Relation", "Description").row("`foo`", "bar")); assertThat(snippets.links())
.isTable((table) -> table.withHeader("Relation", "Description").row("`foo`", "bar"));
} }
@Test @RenderedSnippetTest
public void documentedLinks() throws IOException { void documentedLinks(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
new LinksSnippet(new StubLinkExtractor().withLinks(new Link("a", "alpha"), new Link("b", "bravo")), 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"))) Arrays.asList(new LinkDescriptor("a").description("one"), new LinkDescriptor("b").description("two")))
.document(this.operationBuilder.build()); .document(operationBuilder.build());
assertThat(this.generatedSnippets.links()) assertThat(snippets.links())
.is(tableWithHeader("Relation", "Description").row("`a`", "one").row("`b`", "two")); .isTable((table) -> table.withHeader("Relation", "Description").row("`a`", "one").row("`b`", "two"));
} }
@Test @RenderedSnippetTest
public void linkDescriptionFromTitleInPayload() throws IOException { void linkDescriptionFromTitleInPayload(OperationBuilder operationBuilder, AssertableSnippets snippets)
throws IOException {
new LinksSnippet( new LinksSnippet(
new StubLinkExtractor().withLinks(new Link("a", "alpha", "Link a"), new Link("b", "bravo", "Link b")), 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"))) Arrays.asList(new LinkDescriptor("a").description("one"), new LinkDescriptor("b")))
.document(this.operationBuilder.build()); .document(operationBuilder.build());
assertThat(this.generatedSnippets.links()) assertThat(snippets.links())
.is(tableWithHeader("Relation", "Description").row("`a`", "one").row("`b`", "Link b")); .isTable((table) -> table.withHeader("Relation", "Description").row("`a`", "one").row("`b`", "Link b"));
} }
@Test @RenderedSnippetTest
public void linksWithCustomAttributes() throws IOException { @SnippetTemplate(snippet = "links", template = "links-with-title")
TemplateResourceResolver resolver = mock(TemplateResourceResolver.class); void linksWithCustomAttributes(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
given(resolver.resolveTemplateResource("links")).willReturn(snippetResource("links-with-title"));
new LinksSnippet(new StubLinkExtractor().withLinks(new Link("a", "alpha"), new Link("b", "bravo")), 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")), Arrays.asList(new LinkDescriptor("a").description("one"), new LinkDescriptor("b").description("two")),
attributes(key("title").value("Title for the links"))) attributes(key("title").value("Title for the links")))
.document(this.operationBuilder .document(operationBuilder.build());
.attribute(TemplateEngine.class.getName(), new MustacheTemplateEngine(resolver)) assertThat(snippets.links()).contains("Title for the links");
.build());
assertThat(this.generatedSnippets.links()).contains("Title for the links");
} }
@Test @RenderedSnippetTest
public void linksWithCustomDescriptorAttributes() throws IOException { @SnippetTemplate(snippet = "links", template = "links-with-extra-column")
TemplateResourceResolver resolver = mock(TemplateResourceResolver.class); void linksWithCustomDescriptorAttributes(OperationBuilder operationBuilder, AssertableSnippets snippets)
given(resolver.resolveTemplateResource("links")).willReturn(snippetResource("links-with-extra-column")); throws IOException {
new LinksSnippet(new StubLinkExtractor().withLinks(new Link("a", "alpha"), new Link("b", "bravo")), 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")), Arrays.asList(new LinkDescriptor("a").description("one").attributes(key("foo").value("alpha")),
new LinkDescriptor("b").description("two").attributes(key("foo").value("bravo")))) new LinkDescriptor("b").description("two").attributes(key("foo").value("bravo"))))
.document(this.operationBuilder .document(operationBuilder.build());
.attribute(TemplateEngine.class.getName(), new MustacheTemplateEngine(resolver)) assertThat(snippets.links()).isTable((table) -> table.withHeader("Relation", "Description", "Foo")
.build()); .row("a", "one", "alpha")
assertThat(this.generatedSnippets.links()) .row("b", "two", "bravo"));
.is(tableWithHeader("Relation", "Description", "Foo").row("a", "one", "alpha").row("b", "two", "bravo"));
} }
@Test @RenderedSnippetTest
public void additionalDescriptors() throws IOException { void additionalDescriptors(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
HypermediaDocumentation HypermediaDocumentation
.links(new StubLinkExtractor().withLinks(new Link("a", "alpha"), new Link("b", "bravo")), .links(new StubLinkExtractor().withLinks(new Link("a", "alpha"), new Link("b", "bravo")),
new LinkDescriptor("a").description("one")) new LinkDescriptor("a").description("one"))
.and(new LinkDescriptor("b").description("two")) .and(new LinkDescriptor("b").description("two"))
.document(this.operationBuilder.build()); .document(operationBuilder.build());
assertThat(this.generatedSnippets.links()) assertThat(snippets.links())
.is(tableWithHeader("Relation", "Description").row("`a`", "one").row("`b`", "two")); .isTable((table) -> table.withHeader("Relation", "Description").row("`a`", "one").row("`b`", "two"));
} }
@Test @RenderedSnippetTest
public void tableCellContentIsEscapedWhenNecessary() throws IOException { void tableCellContentIsEscapedWhenNecessary(OperationBuilder operationBuilder, AssertableSnippets snippets)
throws IOException {
new LinksSnippet(new StubLinkExtractor().withLinks(new Link("Foo|Bar", "foo")), new LinksSnippet(new StubLinkExtractor().withLinks(new Link("Foo|Bar", "foo")),
Arrays.asList(new LinkDescriptor("Foo|Bar").description("one|two"))) Arrays.asList(new LinkDescriptor("Foo|Bar").description("one|two")))
.document(this.operationBuilder.build()); .document(operationBuilder.build());
assertThat(this.generatedSnippets.links()).is(tableWithHeader("Relation", "Description") assertThat(snippets.links())
.row(escapeIfNecessary("`Foo|Bar`"), escapeIfNecessary("one|two"))); .isTable((table) -> table.withHeader("Relation", "Description").row("`Foo|Bar`", "one|two"));
} }
private String escapeIfNecessary(String input) { @SnippetTest
if (this.templateFormat.getId().equals(TemplateFormats.markdown().getId())) { void undocumentedLink(OperationBuilder operationBuilder) {
return input; assertThatExceptionOfType(SnippetException.class)
} .isThrownBy(() -> new LinksSnippet(new StubLinkExtractor().withLinks(new Link("foo", "bar")),
return input.replace("|", "\\|"); 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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with 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.net.URI;
import java.util.Collections; import java.util.Collections;
import org.junit.Test; import org.junit.jupiter.api.Test;
import org.springframework.http.HttpHeaders; import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod; import org.springframework.http.HttpMethod;
@@ -39,7 +39,7 @@ import static org.assertj.core.api.Assertions.assertThat;
* @author Andy Wilkinson * @author Andy Wilkinson
* *
*/ */
public class ContentModifyingOperationPreprocessorTests { class ContentModifyingOperationPreprocessorTests {
private final OperationRequestFactory requestFactory = new OperationRequestFactory(); private final OperationRequestFactory requestFactory = new OperationRequestFactory();
@@ -56,7 +56,7 @@ public class ContentModifyingOperationPreprocessorTests {
}); });
@Test @Test
public void modifyRequestContent() { void modifyRequestContent() {
OperationRequest request = this.requestFactory.create(URI.create("http://localhost"), HttpMethod.GET, OperationRequest request = this.requestFactory.create(URI.create("http://localhost"), HttpMethod.GET,
"content".getBytes(), new HttpHeaders(), Collections.<OperationRequestPart>emptyList()); "content".getBytes(), new HttpHeaders(), Collections.<OperationRequestPart>emptyList());
OperationRequest preprocessed = this.preprocessor.preprocess(request); OperationRequest preprocessed = this.preprocessor.preprocess(request);
@@ -64,7 +64,7 @@ public class ContentModifyingOperationPreprocessorTests {
} }
@Test @Test
public void modifyResponseContent() { void modifyResponseContent() {
OperationResponse response = this.responseFactory.create(HttpStatus.OK, new HttpHeaders(), OperationResponse response = this.responseFactory.create(HttpStatus.OK, new HttpHeaders(),
"content".getBytes()); "content".getBytes());
OperationResponse preprocessed = this.preprocessor.preprocess(response); OperationResponse preprocessed = this.preprocessor.preprocess(response);
@@ -72,7 +72,7 @@ public class ContentModifyingOperationPreprocessorTests {
} }
@Test @Test
public void contentLengthIsUpdated() { void contentLengthIsUpdated() {
HttpHeaders httpHeaders = new HttpHeaders(); HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.setContentLength(7); httpHeaders.setContentLength(7);
OperationRequest request = this.requestFactory.create(URI.create("http://localhost"), HttpMethod.GET, 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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with 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 java.util.Arrays;
import org.junit.Test; import org.junit.jupiter.api.Test;
import org.springframework.restdocs.operation.OperationRequest; import org.springframework.restdocs.operation.OperationRequest;
@@ -31,10 +31,10 @@ import static org.mockito.Mockito.mock;
* *
* @author Andy Wilkinson * @author Andy Wilkinson
*/ */
public class DelegatingOperationRequestPreprocessorTests { class DelegatingOperationRequestPreprocessorTests {
@Test @Test
public void delegationOccurs() { void delegationOccurs() {
OperationRequest originalRequest = mock(OperationRequest.class); OperationRequest originalRequest = mock(OperationRequest.class);
OperationPreprocessor preprocessor1 = mock(OperationPreprocessor.class); OperationPreprocessor preprocessor1 = mock(OperationPreprocessor.class);
OperationRequest preprocessedRequest1 = mock(OperationRequest.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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with 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 java.util.Arrays;
import org.junit.Test; import org.junit.jupiter.api.Test;
import org.springframework.restdocs.operation.OperationResponse; import org.springframework.restdocs.operation.OperationResponse;
@@ -31,10 +31,10 @@ import static org.mockito.Mockito.mock;
* *
* @author Andy Wilkinson * @author Andy Wilkinson
*/ */
public class DelegatingOperationResponsePreprocessorTests { class DelegatingOperationResponsePreprocessorTests {
@Test @Test
public void delegationOccurs() { void delegationOccurs() {
OperationResponse originalResponse = mock(OperationResponse.class); OperationResponse originalResponse = mock(OperationResponse.class);
OperationPreprocessor preprocessor1 = mock(OperationPreprocessor.class); OperationPreprocessor preprocessor1 = mock(OperationPreprocessor.class);
OperationResponse preprocessedResponse1 = mock(OperationResponse.class); OperationResponse preprocessedResponse1 = mock(OperationResponse.class);

View File

@@ -22,7 +22,7 @@ import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.function.Consumer; import java.util.function.Consumer;
import org.junit.Test; import org.junit.jupiter.api.Test;
import org.springframework.http.HttpHeaders; import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod; import org.springframework.http.HttpMethod;
@@ -41,12 +41,12 @@ import static org.assertj.core.api.Assertions.entry;
* @author Jihoon Cha * @author Jihoon Cha
* @author Andy Wilkinson * @author Andy Wilkinson
*/ */
public class HeadersModifyingOperationPreprocessorTests { class HeadersModifyingOperationPreprocessorTests {
private final HeadersModifyingOperationPreprocessor preprocessor = new HeadersModifyingOperationPreprocessor(); private final HeadersModifyingOperationPreprocessor preprocessor = new HeadersModifyingOperationPreprocessor();
@Test @Test
public void addNewHeader() { void addNewHeader() {
this.preprocessor.add("a", "alpha"); this.preprocessor.add("a", "alpha");
assertThat(this.preprocessor.preprocess(createRequest()).getHeaders().get("a")) assertThat(this.preprocessor.preprocess(createRequest()).getHeaders().get("a"))
.isEqualTo(Arrays.asList("alpha")); .isEqualTo(Arrays.asList("alpha"));
@@ -55,7 +55,7 @@ public class HeadersModifyingOperationPreprocessorTests {
} }
@Test @Test
public void addValueToExistingHeader() { void addValueToExistingHeader() {
this.preprocessor.add("a", "alpha"); this.preprocessor.add("a", "alpha");
assertThat(this.preprocessor.preprocess(createRequest((headers) -> headers.add("a", "apple"))) assertThat(this.preprocessor.preprocess(createRequest((headers) -> headers.add("a", "apple")))
.getHeaders() .getHeaders()
@@ -66,7 +66,7 @@ public class HeadersModifyingOperationPreprocessorTests {
} }
@Test @Test
public void setNewHeader() { void setNewHeader() {
this.preprocessor.set("a", "alpha", "avocado"); this.preprocessor.set("a", "alpha", "avocado");
assertThat(this.preprocessor.preprocess(createRequest()).getHeaders().headerSet()) assertThat(this.preprocessor.preprocess(createRequest()).getHeaders().headerSet())
.contains(entry("a", Arrays.asList("alpha", "avocado"))); .contains(entry("a", Arrays.asList("alpha", "avocado")));
@@ -75,7 +75,7 @@ public class HeadersModifyingOperationPreprocessorTests {
} }
@Test @Test
public void setExistingHeader() { void setExistingHeader() {
this.preprocessor.set("a", "alpha", "avocado"); this.preprocessor.set("a", "alpha", "avocado");
assertThat(this.preprocessor.preprocess(createRequest((headers) -> headers.add("a", "apple"))) assertThat(this.preprocessor.preprocess(createRequest((headers) -> headers.add("a", "apple")))
.getHeaders() .getHeaders()
@@ -86,14 +86,14 @@ public class HeadersModifyingOperationPreprocessorTests {
} }
@Test @Test
public void removeNonExistentHeader() { void removeNonExistentHeader() {
this.preprocessor.remove("a"); this.preprocessor.remove("a");
assertThat(this.preprocessor.preprocess(createRequest()).getHeaders().headerNames()).doesNotContain("a"); assertThat(this.preprocessor.preprocess(createRequest()).getHeaders().headerNames()).doesNotContain("a");
assertThat(this.preprocessor.preprocess(createResponse()).getHeaders().headerNames()).doesNotContain("a"); assertThat(this.preprocessor.preprocess(createResponse()).getHeaders().headerNames()).doesNotContain("a");
} }
@Test @Test
public void removeHeader() { void removeHeader() {
this.preprocessor.remove("a"); this.preprocessor.remove("a");
assertThat(this.preprocessor.preprocess(createRequest((headers) -> headers.add("a", "apple"))) assertThat(this.preprocessor.preprocess(createRequest((headers) -> headers.add("a", "apple")))
.getHeaders() .getHeaders()
@@ -104,14 +104,14 @@ public class HeadersModifyingOperationPreprocessorTests {
} }
@Test @Test
public void removeHeaderValueForNonExistentHeader() { void removeHeaderValueForNonExistentHeader() {
this.preprocessor.remove("a", "apple"); this.preprocessor.remove("a", "apple");
assertThat(this.preprocessor.preprocess(createRequest()).getHeaders().headerNames()).doesNotContain("a"); assertThat(this.preprocessor.preprocess(createRequest()).getHeaders().headerNames()).doesNotContain("a");
assertThat(this.preprocessor.preprocess(createResponse()).getHeaders().headerNames()).doesNotContain("a"); assertThat(this.preprocessor.preprocess(createResponse()).getHeaders().headerNames()).doesNotContain("a");
} }
@Test @Test
public void removeHeaderValueWithMultipleValues() { void removeHeaderValueWithMultipleValues() {
this.preprocessor.remove("a", "apple"); this.preprocessor.remove("a", "apple");
assertThat( assertThat(
this.preprocessor.preprocess(createRequest((headers) -> headers.addAll("a", List.of("apple", "alpha")))) this.preprocessor.preprocess(createRequest((headers) -> headers.addAll("a", List.of("apple", "alpha"))))
@@ -125,7 +125,7 @@ public class HeadersModifyingOperationPreprocessorTests {
} }
@Test @Test
public void removeHeaderValueWithSingleValueRemovesEntryEntirely() { void removeHeaderValueWithSingleValueRemovesEntryEntirely() {
this.preprocessor.remove("a", "apple"); this.preprocessor.remove("a", "apple");
assertThat(this.preprocessor.preprocess(createRequest((headers) -> headers.add("a", "apple"))) assertThat(this.preprocessor.preprocess(createRequest((headers) -> headers.add("a", "apple")))
.getHeaders() .getHeaders()
@@ -136,7 +136,7 @@ public class HeadersModifyingOperationPreprocessorTests {
} }
@Test @Test
public void removeHeadersByNamePattern() { void removeHeadersByNamePattern() {
Consumer<HttpHeaders> headersCustomizer = (headers) -> { Consumer<HttpHeaders> headersCustomizer = (headers) -> {
headers.add("apple", "apple"); headers.add("apple", "apple");
headers.add("alpha", "alpha"); 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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with 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.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature; import com.fasterxml.jackson.databind.SerializationFeature;
import org.junit.Test; import org.junit.jupiter.api.Test;
import org.springframework.restdocs.hypermedia.Link; import org.springframework.restdocs.hypermedia.Link;
@@ -38,7 +38,7 @@ import static org.assertj.core.api.Assertions.assertThat;
* @author Andy Wilkinson * @author Andy Wilkinson
* *
*/ */
public class LinkMaskingContentModifierTests { class LinkMaskingContentModifierTests {
private final ContentModifier contentModifier = new LinkMaskingContentModifier(); 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", "...") }; private final Link[] maskedLinks = new Link[] { new Link("a", "..."), new Link("b", "...") };
@Test @Test
public void halLinksAreMasked() throws Exception { void halLinksAreMasked() throws Exception {
assertThat(this.contentModifier.modifyContent(halPayloadWithLinks(this.links), null)) assertThat(this.contentModifier.modifyContent(halPayloadWithLinks(this.links), null))
.isEqualTo(halPayloadWithLinks(this.maskedLinks)); .isEqualTo(halPayloadWithLinks(this.maskedLinks));
} }
@Test @Test
public void formattedHalLinksAreMasked() throws Exception { void formattedHalLinksAreMasked() throws Exception {
assertThat(this.contentModifier.modifyContent(formattedHalPayloadWithLinks(this.links), null)) assertThat(this.contentModifier.modifyContent(formattedHalPayloadWithLinks(this.links), null))
.isEqualTo(formattedHalPayloadWithLinks(this.maskedLinks)); .isEqualTo(formattedHalPayloadWithLinks(this.maskedLinks));
} }
@Test @Test
public void atomLinksAreMasked() throws Exception { void atomLinksAreMasked() throws Exception {
assertThat(this.contentModifier.modifyContent(atomPayloadWithLinks(this.links), null)) assertThat(this.contentModifier.modifyContent(atomPayloadWithLinks(this.links), null))
.isEqualTo(atomPayloadWithLinks(this.maskedLinks)); .isEqualTo(atomPayloadWithLinks(this.maskedLinks));
} }
@Test @Test
public void formattedAtomLinksAreMasked() throws Exception { void formattedAtomLinksAreMasked() throws Exception {
assertThat(this.contentModifier.modifyContent(formattedAtomPayloadWithLinks(this.links), null)) assertThat(this.contentModifier.modifyContent(formattedAtomPayloadWithLinks(this.links), null))
.isEqualTo(formattedAtomPayloadWithLinks(this.maskedLinks)); .isEqualTo(formattedAtomPayloadWithLinks(this.maskedLinks));
} }
@Test @Test
public void maskCanBeCustomized() throws Exception { void maskCanBeCustomized() throws Exception {
assertThat( assertThat(
new LinkMaskingContentModifier("custom").modifyContent(formattedAtomPayloadWithLinks(this.links), null)) new LinkMaskingContentModifier("custom").modifyContent(formattedAtomPayloadWithLinks(this.links), null))
.isEqualTo(formattedAtomPayloadWithLinks(new Link("a", "custom"), new Link("b", "custom"))); .isEqualTo(formattedAtomPayloadWithLinks(new Link("a", "custom"), new Link("b", "custom")));
} }
@Test @Test
public void maskCanUseUtf8Characters() throws Exception { void maskCanUseUtf8Characters() throws Exception {
String ellipsis = "\u2026"; String ellipsis = "\u2026";
assertThat( assertThat(
new LinkMaskingContentModifier(ellipsis).modifyContent(formattedHalPayloadWithLinks(this.links), null)) 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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with 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.nio.charset.StandardCharsets;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import org.junit.Test; import org.junit.jupiter.api.Test;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
@@ -31,10 +31,10 @@ import static org.assertj.core.api.Assertions.assertThat;
* *
* @author Andy Wilkinson * @author Andy Wilkinson
*/ */
public class PatternReplacingContentModifierTests { class PatternReplacingContentModifierTests {
@Test @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 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); Pattern.CASE_INSENSITIVE);
PatternReplacingContentModifier contentModifier = new PatternReplacingContentModifier(pattern, "<<uuid>>"); PatternReplacingContentModifier contentModifier = new PatternReplacingContentModifier(pattern, "<<uuid>>");
@@ -44,7 +44,7 @@ public class PatternReplacingContentModifierTests {
} }
@Test @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 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); Pattern.CASE_INSENSITIVE);
PatternReplacingContentModifier contentModifier = new PatternReplacingContentModifier(pattern, "<<uuid>>"); PatternReplacingContentModifier contentModifier = new PatternReplacingContentModifier(pattern, "<<uuid>>");
@@ -53,7 +53,7 @@ public class PatternReplacingContentModifierTests {
} }
@Test @Test
public void encodingIsPreservedUsingCharsetFromContentType() { void encodingIsPreservedUsingCharsetFromContentType() {
String japaneseContent = "\u30b3\u30f3\u30c6\u30f3\u30c4"; String japaneseContent = "\u30b3\u30f3\u30c6\u30f3\u30c4";
Pattern pattern = Pattern.compile("[0-9]+"); Pattern pattern = Pattern.compile("[0-9]+");
PatternReplacingContentModifier contentModifier = new PatternReplacingContentModifier(pattern, "<<number>>"); PatternReplacingContentModifier contentModifier = new PatternReplacingContentModifier(pattern, "<<number>>");
@@ -63,7 +63,7 @@ public class PatternReplacingContentModifierTests {
} }
@Test @Test
public void encodingIsPreservedUsingFallbackCharset() { void encodingIsPreservedUsingFallbackCharset() {
String japaneseContent = "\u30b3\u30f3\u30c6\u30f3\u30c4"; String japaneseContent = "\u30b3\u30f3\u30c6\u30f3\u30c4";
Pattern pattern = Pattern.compile("[0-9]+"); Pattern pattern = Pattern.compile("[0-9]+");
PatternReplacingContentModifier contentModifier = new PatternReplacingContentModifier(pattern, "<<number>>", 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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with 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 java.util.Map;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.Rule; import org.junit.jupiter.api.Test;
import org.junit.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; import static org.assertj.core.api.Assertions.assertThat;
@@ -33,19 +34,17 @@ import static org.assertj.core.api.Assertions.assertThat;
* @author Andy Wilkinson * @author Andy Wilkinson
* *
*/ */
public class PrettyPrintingContentModifierTests { @ExtendWith(OutputCaptureExtension.class)
class PrettyPrintingContentModifierTests {
@Rule
public OutputCaptureRule outputCapture = new OutputCaptureRule();
@Test @Test
public void prettyPrintJson() { void prettyPrintJson() {
assertThat(new PrettyPrintingContentModifier().modifyContent("{\"a\":5}".getBytes(), null)) assertThat(new PrettyPrintingContentModifier().modifyContent("{\"a\":5}".getBytes(), null))
.isEqualTo(String.format("{%n \"a\" : 5%n}").getBytes()); .isEqualTo(String.format("{%n \"a\" : 5%n}").getBytes());
} }
@Test @Test
public void prettyPrintXml() { void prettyPrintXml() {
assertThat(new PrettyPrintingContentModifier() assertThat(new PrettyPrintingContentModifier()
.modifyContent("<one a=\"alpha\"><two b=\"bravo\"/></one>".getBytes(), null)) .modifyContent("<one a=\"alpha\"><two b=\"bravo\"/></one>".getBytes(), null))
.isEqualTo(String .isEqualTo(String
@@ -55,28 +54,28 @@ public class PrettyPrintingContentModifierTests {
} }
@Test @Test
public void empytContentIsHandledGracefully() { void empytContentIsHandledGracefully() {
assertThat(new PrettyPrintingContentModifier().modifyContent("".getBytes(), null)).isEqualTo("".getBytes()); assertThat(new PrettyPrintingContentModifier().modifyContent("".getBytes(), null)).isEqualTo("".getBytes());
} }
@Test @Test
public void nonJsonAndNonXmlContentIsHandledGracefully() { void nonJsonAndNonXmlContentIsHandledGracefully(CapturedOutput output) {
String content = "abcdefg"; String content = "abcdefg";
assertThat(new PrettyPrintingContentModifier().modifyContent(content.getBytes(), null)) assertThat(new PrettyPrintingContentModifier().modifyContent(content.getBytes(), null))
.isEqualTo(content.getBytes()); .isEqualTo(content.getBytes());
assertThat(this.outputCapture).isEmpty(); assertThat(output).isEmpty();
} }
@Test @Test
public void nonJsonContentThatInitiallyLooksLikeJsonIsHandledGracefully() { void nonJsonContentThatInitiallyLooksLikeJsonIsHandledGracefully(CapturedOutput output) {
String content = "\"abc\",\"def\""; String content = "\"abc\",\"def\"";
assertThat(new PrettyPrintingContentModifier().modifyContent(content.getBytes(), null)) assertThat(new PrettyPrintingContentModifier().modifyContent(content.getBytes(), null))
.isEqualTo(content.getBytes()); .isEqualTo(content.getBytes());
assertThat(this.outputCapture).isEmpty(); assertThat(output).isEmpty();
} }
@Test @Test
public void encodingIsPreserved() throws Exception { void encodingIsPreserved() throws Exception {
Map<String, String> input = new HashMap<>(); Map<String, String> input = new HashMap<>();
input.put("japanese", "\u30b3\u30f3\u30c6\u30f3\u30c4"); input.put("japanese", "\u30b3\u30f3\u30c6\u30f3\u30c4");
ObjectMapper objectMapper = new ObjectMapper(); 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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with 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.Collections;
import java.util.List; import java.util.List;
import org.junit.Test; import org.junit.jupiter.api.Test;
import org.springframework.http.HttpHeaders; import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod; import org.springframework.http.HttpMethod;
@@ -41,7 +41,7 @@ import static org.assertj.core.api.Assertions.assertThat;
* *
* @author Andy Wilkinson * @author Andy Wilkinson
*/ */
public class UriModifyingOperationPreprocessorTests { class UriModifyingOperationPreprocessorTests {
private final OperationRequestFactory requestFactory = new OperationRequestFactory(); private final OperationRequestFactory requestFactory = new OperationRequestFactory();
@@ -50,14 +50,14 @@ public class UriModifyingOperationPreprocessorTests {
private final UriModifyingOperationPreprocessor preprocessor = new UriModifyingOperationPreprocessor(); private final UriModifyingOperationPreprocessor preprocessor = new UriModifyingOperationPreprocessor();
@Test @Test
public void requestUriSchemeCanBeModified() { void requestUriSchemeCanBeModified() {
this.preprocessor.scheme("https"); this.preprocessor.scheme("https");
OperationRequest processed = this.preprocessor.preprocess(createRequestWithUri("http://localhost:12345")); OperationRequest processed = this.preprocessor.preprocess(createRequestWithUri("http://localhost:12345"));
assertThat(processed.getUri()).isEqualTo(URI.create("https://localhost:12345")); assertThat(processed.getUri()).isEqualTo(URI.create("https://localhost:12345"));
} }
@Test @Test
public void requestUriHostCanBeModified() { void requestUriHostCanBeModified() {
this.preprocessor.host("api.example.com"); this.preprocessor.host("api.example.com");
OperationRequest processed = this.preprocessor.preprocess(createRequestWithUri("https://api.foo.com:12345")); OperationRequest processed = this.preprocessor.preprocess(createRequestWithUri("https://api.foo.com:12345"));
assertThat(processed.getUri()).isEqualTo(URI.create("https://api.example.com:12345")); assertThat(processed.getUri()).isEqualTo(URI.create("https://api.example.com:12345"));
@@ -65,7 +65,7 @@ public class UriModifyingOperationPreprocessorTests {
} }
@Test @Test
public void requestUriPortCanBeModified() { void requestUriPortCanBeModified() {
this.preprocessor.port(23456); this.preprocessor.port(23456);
OperationRequest processed = this.preprocessor OperationRequest processed = this.preprocessor
.preprocess(createRequestWithUri("https://api.example.com:12345")); .preprocess(createRequestWithUri("https://api.example.com:12345"));
@@ -74,7 +74,7 @@ public class UriModifyingOperationPreprocessorTests {
} }
@Test @Test
public void requestUriPortCanBeRemoved() { void requestUriPortCanBeRemoved() {
this.preprocessor.removePort(); this.preprocessor.removePort();
OperationRequest processed = this.preprocessor OperationRequest processed = this.preprocessor
.preprocess(createRequestWithUri("https://api.example.com:12345")); .preprocess(createRequestWithUri("https://api.example.com:12345"));
@@ -83,7 +83,7 @@ public class UriModifyingOperationPreprocessorTests {
} }
@Test @Test
public void requestUriPathIsPreserved() { void requestUriPathIsPreserved() {
this.preprocessor.removePort(); this.preprocessor.removePort();
OperationRequest processed = this.preprocessor OperationRequest processed = this.preprocessor
.preprocess(createRequestWithUri("https://api.example.com:12345/foo/bar")); .preprocess(createRequestWithUri("https://api.example.com:12345/foo/bar"));
@@ -91,7 +91,7 @@ public class UriModifyingOperationPreprocessorTests {
} }
@Test @Test
public void requestUriQueryIsPreserved() { void requestUriQueryIsPreserved() {
this.preprocessor.removePort(); this.preprocessor.removePort();
OperationRequest processed = this.preprocessor OperationRequest processed = this.preprocessor
.preprocess(createRequestWithUri("https://api.example.com:12345?foo=bar")); .preprocess(createRequestWithUri("https://api.example.com:12345?foo=bar"));
@@ -99,7 +99,7 @@ public class UriModifyingOperationPreprocessorTests {
} }
@Test @Test
public void requestUriAnchorIsPreserved() { void requestUriAnchorIsPreserved() {
this.preprocessor.removePort(); this.preprocessor.removePort();
OperationRequest processed = this.preprocessor OperationRequest processed = this.preprocessor
.preprocess(createRequestWithUri("https://api.example.com:12345#foo")); .preprocess(createRequestWithUri("https://api.example.com:12345#foo"));
@@ -107,7 +107,7 @@ public class UriModifyingOperationPreprocessorTests {
} }
@Test @Test
public void requestContentUriSchemeCanBeModified() { void requestContentUriSchemeCanBeModified() {
this.preprocessor.scheme("https"); this.preprocessor.scheme("https");
OperationRequest processed = this.preprocessor.preprocess(createRequestWithContent( OperationRequest processed = this.preprocessor.preprocess(createRequestWithContent(
"The uri 'https://localhost:12345' should be used. foo:bar will be unaffected")); "The uri 'https://localhost:12345' should be used. foo:bar will be unaffected"));
@@ -116,7 +116,7 @@ public class UriModifyingOperationPreprocessorTests {
} }
@Test @Test
public void requestContentUriHostCanBeModified() { void requestContentUriHostCanBeModified() {
this.preprocessor.host("api.example.com"); this.preprocessor.host("api.example.com");
OperationRequest processed = this.preprocessor.preprocess(createRequestWithContent( OperationRequest processed = this.preprocessor.preprocess(createRequestWithContent(
"The uri 'https://localhost:12345' should be used. foo:bar will be unaffected")); "The uri 'https://localhost:12345' should be used. foo:bar will be unaffected"));
@@ -125,7 +125,7 @@ public class UriModifyingOperationPreprocessorTests {
} }
@Test @Test
public void requestContentHostOfUriWithoutPortCanBeModified() { void requestContentHostOfUriWithoutPortCanBeModified() {
this.preprocessor.host("api.example.com"); this.preprocessor.host("api.example.com");
OperationRequest processed = this.preprocessor.preprocess( OperationRequest processed = this.preprocessor.preprocess(
createRequestWithContent("The uri 'https://localhost' should be used. foo:bar will be unaffected")); createRequestWithContent("The uri 'https://localhost' should be used. foo:bar will be unaffected"));
@@ -134,7 +134,7 @@ public class UriModifyingOperationPreprocessorTests {
} }
@Test @Test
public void requestContentUriPortCanBeAdded() { void requestContentUriPortCanBeAdded() {
this.preprocessor.port(23456); this.preprocessor.port(23456);
OperationRequest processed = this.preprocessor.preprocess( OperationRequest processed = this.preprocessor.preprocess(
createRequestWithContent("The uri 'http://localhost' should be used. foo:bar will be unaffected")); createRequestWithContent("The uri 'http://localhost' should be used. foo:bar will be unaffected"));
@@ -143,7 +143,7 @@ public class UriModifyingOperationPreprocessorTests {
} }
@Test @Test
public void requestContentUriPortCanBeModified() { void requestContentUriPortCanBeModified() {
this.preprocessor.port(23456); this.preprocessor.port(23456);
OperationRequest processed = this.preprocessor.preprocess(createRequestWithContent( OperationRequest processed = this.preprocessor.preprocess(createRequestWithContent(
"The uri 'http://localhost:12345' should be used. foo:bar will be unaffected")); "The uri 'http://localhost:12345' should be used. foo:bar will be unaffected"));
@@ -152,7 +152,7 @@ public class UriModifyingOperationPreprocessorTests {
} }
@Test @Test
public void requestContentUriPortCanBeRemoved() { void requestContentUriPortCanBeRemoved() {
this.preprocessor.removePort(); this.preprocessor.removePort();
OperationRequest processed = this.preprocessor.preprocess(createRequestWithContent( OperationRequest processed = this.preprocessor.preprocess(createRequestWithContent(
"The uri 'http://localhost:12345' should be used. foo:bar will be unaffected")); "The uri 'http://localhost:12345' should be used. foo:bar will be unaffected"));
@@ -161,7 +161,7 @@ public class UriModifyingOperationPreprocessorTests {
} }
@Test @Test
public void multipleRequestContentUrisCanBeModified() { void multipleRequestContentUrisCanBeModified() {
this.preprocessor.removePort(); this.preprocessor.removePort();
OperationRequest processed = this.preprocessor.preprocess(createRequestWithContent( OperationRequest processed = this.preprocessor.preprocess(createRequestWithContent(
"Use 'http://localhost:12345' or 'https://localhost:23456' to access the service")); "Use 'http://localhost:12345' or 'https://localhost:23456' to access the service"));
@@ -170,7 +170,7 @@ public class UriModifyingOperationPreprocessorTests {
} }
@Test @Test
public void requestContentUriPathIsPreserved() { void requestContentUriPathIsPreserved() {
this.preprocessor.removePort(); this.preprocessor.removePort();
OperationRequest processed = this.preprocessor OperationRequest processed = this.preprocessor
.preprocess(createRequestWithContent("The uri 'http://localhost:12345/foo/bar' should be used")); .preprocess(createRequestWithContent("The uri 'http://localhost:12345/foo/bar' should be used"));
@@ -178,7 +178,7 @@ public class UriModifyingOperationPreprocessorTests {
} }
@Test @Test
public void requestContentUriQueryIsPreserved() { void requestContentUriQueryIsPreserved() {
this.preprocessor.removePort(); this.preprocessor.removePort();
OperationRequest processed = this.preprocessor OperationRequest processed = this.preprocessor
.preprocess(createRequestWithContent("The uri 'http://localhost:12345?foo=bar' should be used")); .preprocess(createRequestWithContent("The uri 'http://localhost:12345?foo=bar' should be used"));
@@ -186,7 +186,7 @@ public class UriModifyingOperationPreprocessorTests {
} }
@Test @Test
public void requestContentUriAnchorIsPreserved() { void requestContentUriAnchorIsPreserved() {
this.preprocessor.removePort(); this.preprocessor.removePort();
OperationRequest processed = this.preprocessor OperationRequest processed = this.preprocessor
.preprocess(createRequestWithContent("The uri 'http://localhost:12345#foo' should be used")); .preprocess(createRequestWithContent("The uri 'http://localhost:12345#foo' should be used"));
@@ -194,7 +194,7 @@ public class UriModifyingOperationPreprocessorTests {
} }
@Test @Test
public void responseContentUriSchemeCanBeModified() { void responseContentUriSchemeCanBeModified() {
this.preprocessor.scheme("https"); this.preprocessor.scheme("https");
OperationResponse processed = this.preprocessor OperationResponse processed = this.preprocessor
.preprocess(createResponseWithContent("The uri 'http://localhost:12345' should be used")); .preprocess(createResponseWithContent("The uri 'http://localhost:12345' should be used"));
@@ -202,7 +202,7 @@ public class UriModifyingOperationPreprocessorTests {
} }
@Test @Test
public void responseContentUriHostCanBeModified() { void responseContentUriHostCanBeModified() {
this.preprocessor.host("api.example.com"); this.preprocessor.host("api.example.com");
OperationResponse processed = this.preprocessor OperationResponse processed = this.preprocessor
.preprocess(createResponseWithContent("The uri 'https://localhost:12345' should be used")); .preprocess(createResponseWithContent("The uri 'https://localhost:12345' should be used"));
@@ -211,7 +211,7 @@ public class UriModifyingOperationPreprocessorTests {
} }
@Test @Test
public void responseContentUriPortCanBeModified() { void responseContentUriPortCanBeModified() {
this.preprocessor.port(23456); this.preprocessor.port(23456);
OperationResponse processed = this.preprocessor OperationResponse processed = this.preprocessor
.preprocess(createResponseWithContent("The uri 'http://localhost:12345' should be used")); .preprocess(createResponseWithContent("The uri 'http://localhost:12345' should be used"));
@@ -219,7 +219,7 @@ public class UriModifyingOperationPreprocessorTests {
} }
@Test @Test
public void responseContentUriPortCanBeRemoved() { void responseContentUriPortCanBeRemoved() {
this.preprocessor.removePort(); this.preprocessor.removePort();
OperationResponse processed = this.preprocessor OperationResponse processed = this.preprocessor
.preprocess(createResponseWithContent("The uri 'http://localhost:12345' should be used")); .preprocess(createResponseWithContent("The uri 'http://localhost:12345' should be used"));
@@ -227,7 +227,7 @@ public class UriModifyingOperationPreprocessorTests {
} }
@Test @Test
public void multipleResponseContentUrisCanBeModified() { void multipleResponseContentUrisCanBeModified() {
this.preprocessor.removePort(); this.preprocessor.removePort();
OperationResponse processed = this.preprocessor.preprocess(createResponseWithContent( OperationResponse processed = this.preprocessor.preprocess(createResponseWithContent(
"Use 'http://localhost:12345' or 'https://localhost:23456' to access the service")); "Use 'http://localhost:12345' or 'https://localhost:23456' to access the service"));
@@ -236,7 +236,7 @@ public class UriModifyingOperationPreprocessorTests {
} }
@Test @Test
public void responseContentUriPathIsPreserved() { void responseContentUriPathIsPreserved() {
this.preprocessor.removePort(); this.preprocessor.removePort();
OperationResponse processed = this.preprocessor OperationResponse processed = this.preprocessor
.preprocess(createResponseWithContent("The uri 'http://localhost:12345/foo/bar' should be used")); .preprocess(createResponseWithContent("The uri 'http://localhost:12345/foo/bar' should be used"));
@@ -244,7 +244,7 @@ public class UriModifyingOperationPreprocessorTests {
} }
@Test @Test
public void responseContentUriQueryIsPreserved() { void responseContentUriQueryIsPreserved() {
this.preprocessor.removePort(); this.preprocessor.removePort();
OperationResponse processed = this.preprocessor OperationResponse processed = this.preprocessor
.preprocess(createResponseWithContent("The uri 'http://localhost:12345?foo=bar' should be used")); .preprocess(createResponseWithContent("The uri 'http://localhost:12345?foo=bar' should be used"));
@@ -252,7 +252,7 @@ public class UriModifyingOperationPreprocessorTests {
} }
@Test @Test
public void responseContentUriAnchorIsPreserved() { void responseContentUriAnchorIsPreserved() {
this.preprocessor.removePort(); this.preprocessor.removePort();
OperationResponse processed = this.preprocessor OperationResponse processed = this.preprocessor
.preprocess(createResponseWithContent("The uri 'http://localhost:12345#foo' should be used")); .preprocess(createResponseWithContent("The uri 'http://localhost:12345#foo' should be used"));
@@ -260,7 +260,7 @@ public class UriModifyingOperationPreprocessorTests {
} }
@Test @Test
public void urisInRequestHeadersCanBeModified() { void urisInRequestHeadersCanBeModified() {
OperationRequest processed = this.preprocessor.host("api.example.com") OperationRequest processed = this.preprocessor.host("api.example.com")
.preprocess(createRequestWithHeader("Foo", "https://locahost:12345")); .preprocess(createRequestWithHeader("Foo", "https://locahost:12345"));
assertThat(processed.getHeaders().getFirst("Foo")).isEqualTo("https://api.example.com:12345"); assertThat(processed.getHeaders().getFirst("Foo")).isEqualTo("https://api.example.com:12345");
@@ -268,14 +268,14 @@ public class UriModifyingOperationPreprocessorTests {
} }
@Test @Test
public void urisInResponseHeadersCanBeModified() { void urisInResponseHeadersCanBeModified() {
OperationResponse processed = this.preprocessor.host("api.example.com") OperationResponse processed = this.preprocessor.host("api.example.com")
.preprocess(createResponseWithHeader("Foo", "https://locahost:12345")); .preprocess(createResponseWithHeader("Foo", "https://locahost:12345"));
assertThat(processed.getHeaders().getFirst("Foo")).isEqualTo("https://api.example.com:12345"); assertThat(processed.getHeaders().getFirst("Foo")).isEqualTo("https://api.example.com:12345");
} }
@Test @Test
public void urisInRequestPartHeadersCanBeModified() { void urisInRequestPartHeadersCanBeModified() {
OperationRequest processed = this.preprocessor.host("api.example.com") OperationRequest processed = this.preprocessor.host("api.example.com")
.preprocess(createRequestWithPartWithHeader("Foo", "https://locahost:12345")); .preprocess(createRequestWithPartWithHeader("Foo", "https://locahost:12345"));
assertThat(processed.getParts().iterator().next().getHeaders().getFirst("Foo")) assertThat(processed.getParts().iterator().next().getHeaders().getFirst("Foo"))
@@ -283,7 +283,7 @@ public class UriModifyingOperationPreprocessorTests {
} }
@Test @Test
public void urisInRequestPartContentCanBeModified() { void urisInRequestPartContentCanBeModified() {
OperationRequest processed = this.preprocessor.host("api.example.com") OperationRequest processed = this.preprocessor.host("api.example.com")
.preprocess(createRequestWithPartWithContent("The uri 'https://localhost:12345' should be used")); .preprocess(createRequestWithPartWithContent("The uri 'https://localhost:12345' should be used"));
assertThat(new String(processed.getParts().iterator().next().getContent())) assertThat(new String(processed.getParts().iterator().next().getContent()))
@@ -291,7 +291,7 @@ public class UriModifyingOperationPreprocessorTests {
} }
@Test @Test
public void modifiedUriDoesNotGetDoubleEncoded() { void modifiedUriDoesNotGetDoubleEncoded() {
this.preprocessor.scheme("https"); this.preprocessor.scheme("https");
OperationRequest processed = this.preprocessor OperationRequest processed = this.preprocessor
.preprocess(createRequestWithUri("http://localhost:12345?foo=%7B%7D")); .preprocess(createRequestWithUri("http://localhost:12345?foo=%7B%7D"));
@@ -300,7 +300,7 @@ public class UriModifyingOperationPreprocessorTests {
} }
@Test @Test
public void resultingRequestHasCookiesFromOriginalRequst() { void resultingRequestHasCookiesFromOriginalRequst() {
List<RequestCookie> cookies = Arrays.asList(new RequestCookie("a", "alpha")); List<RequestCookie> cookies = Arrays.asList(new RequestCookie("a", "alpha"));
OperationRequest request = this.requestFactory.create(URI.create("http://localhost:12345"), HttpMethod.GET, OperationRequest request = this.requestFactory.create(URI.create("http://localhost:12345"), HttpMethod.GET,
new byte[0], new HttpHeaders(), Collections.<OperationRequestPart>emptyList(), cookies); 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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with 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.io.IOException;
import java.util.Arrays; import java.util.Arrays;
import org.junit.Rule; import org.springframework.restdocs.testfixtures.jupiter.AssertableSnippets;
import org.junit.Test; import org.springframework.restdocs.testfixtures.jupiter.OperationBuilder;
import org.springframework.restdocs.testfixtures.jupiter.RenderedSnippetTest;
import org.springframework.core.io.FileSystemResource; import org.springframework.restdocs.testfixtures.jupiter.RenderedSnippetTest.Format;
import org.springframework.restdocs.templates.TemplateEngine; import org.springframework.restdocs.testfixtures.jupiter.SnippetTemplate;
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 static org.assertj.core.api.Assertions.assertThat; 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; import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath;
/** /**
@@ -41,33 +33,17 @@ import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWit
* *
* @author Andy Wilkinson * @author Andy Wilkinson
*/ */
public class AsciidoctorRequestFieldsSnippetTests { class AsciidoctorRequestFieldsSnippetTests {
@Rule @RenderedSnippetTest(format = Format.ASCIIDOCTOR)
public OperationBuilder operationBuilder = new OperationBuilder(TemplateFormats.asciidoctor()); @SnippetTemplate(snippet = "request-fields", template = "request-fields-with-list-description")
void requestFieldsWithListDescription(OperationBuilder operationBuilder, AssertableSnippets snippets)
@Rule throws IOException {
public GeneratedSnippets generatedSnippets = new GeneratedSnippets(TemplateFormats.asciidoctor()); new RequestFieldsSnippet(Arrays.asList(fieldWithPath("a").description(Arrays.asList("one", "two"))))
.document(operationBuilder.request("http://localhost").content("{\"a\": \"foo\"}").build());
@Test assertThat(snippets.requestFields()).isTable((table) -> table.withHeader("Path", "Type", "Description")
public void requestFieldsWithListDescription() throws IOException { .row("a", "String", String.format(" - one%n - two"))
TemplateResourceResolver resolver = mock(TemplateResourceResolver.class); .configuration("[cols=\"1,1,1a\"]"));
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");
} }
} }

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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with 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.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature; import com.fasterxml.jackson.databind.SerializationFeature;
import org.junit.Test; import org.junit.jupiter.api.Test;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
@@ -38,11 +38,11 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy;
* *
* @author Andy Wilkinson * @author Andy Wilkinson
*/ */
public class FieldPathPayloadSubsectionExtractorTests { class FieldPathPayloadSubsectionExtractorTests {
@Test @Test
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public void extractMapSubsectionOfJsonMap() throws JsonParseException, JsonMappingException, IOException { void extractMapSubsectionOfJsonMap() throws JsonParseException, JsonMappingException, IOException {
byte[] extractedPayload = new FieldPathPayloadSubsectionExtractor("a.b") byte[] extractedPayload = new FieldPathPayloadSubsectionExtractor("a.b")
.extractSubsection("{\"a\":{\"b\":{\"c\":5}}}".getBytes(), MediaType.APPLICATION_JSON); .extractSubsection("{\"a\":{\"b\":{\"c\":5}}}".getBytes(), MediaType.APPLICATION_JSON);
Map<String, Object> extracted = new ObjectMapper().readValue(extractedPayload, Map.class); Map<String, Object> extracted = new ObjectMapper().readValue(extractedPayload, Map.class);
@@ -52,8 +52,7 @@ public class FieldPathPayloadSubsectionExtractorTests {
@Test @Test
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public void extractSingleElementArraySubsectionOfJsonMap() void extractSingleElementArraySubsectionOfJsonMap() throws JsonParseException, JsonMappingException, IOException {
throws JsonParseException, JsonMappingException, IOException {
byte[] extractedPayload = new FieldPathPayloadSubsectionExtractor("a.[]") byte[] extractedPayload = new FieldPathPayloadSubsectionExtractor("a.[]")
.extractSubsection("{\"a\":[{\"b\":5}]}".getBytes(), MediaType.APPLICATION_JSON); .extractSubsection("{\"a\":[{\"b\":5}]}".getBytes(), MediaType.APPLICATION_JSON);
Map<String, Object> extracted = new ObjectMapper().readValue(extractedPayload, Map.class); Map<String, Object> extracted = new ObjectMapper().readValue(extractedPayload, Map.class);
@@ -63,8 +62,7 @@ public class FieldPathPayloadSubsectionExtractorTests {
@Test @Test
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public void extractMultiElementArraySubsectionOfJsonMap() void extractMultiElementArraySubsectionOfJsonMap() throws JsonParseException, JsonMappingException, IOException {
throws JsonParseException, JsonMappingException, IOException {
byte[] extractedPayload = new FieldPathPayloadSubsectionExtractor("a") byte[] extractedPayload = new FieldPathPayloadSubsectionExtractor("a")
.extractSubsection("{\"a\":[{\"b\":5},{\"b\":4}]}".getBytes(), MediaType.APPLICATION_JSON); .extractSubsection("{\"a\":[{\"b\":5},{\"b\":4}]}".getBytes(), MediaType.APPLICATION_JSON);
Map<String, Object> extracted = new ObjectMapper().readValue(extractedPayload, Map.class); Map<String, Object> extracted = new ObjectMapper().readValue(extractedPayload, Map.class);
@@ -74,7 +72,7 @@ public class FieldPathPayloadSubsectionExtractorTests {
@Test @Test
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public void extractMapSubsectionFromSingleElementArrayInAJsonMap() void extractMapSubsectionFromSingleElementArrayInAJsonMap()
throws JsonParseException, JsonMappingException, IOException { throws JsonParseException, JsonMappingException, IOException {
byte[] extractedPayload = new FieldPathPayloadSubsectionExtractor("a.[].b") byte[] extractedPayload = new FieldPathPayloadSubsectionExtractor("a.[].b")
.extractSubsection("{\"a\":[{\"b\":{\"c\":5}}]}".getBytes(), MediaType.APPLICATION_JSON); .extractSubsection("{\"a\":[{\"b\":{\"c\":5}}]}".getBytes(), MediaType.APPLICATION_JSON);
@@ -85,7 +83,7 @@ public class FieldPathPayloadSubsectionExtractorTests {
@Test @Test
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public void extractMapSubsectionWithCommonStructureFromMultiElementArrayInAJsonMap() void extractMapSubsectionWithCommonStructureFromMultiElementArrayInAJsonMap()
throws JsonParseException, JsonMappingException, IOException { throws JsonParseException, JsonMappingException, IOException {
byte[] extractedPayload = new FieldPathPayloadSubsectionExtractor("a.[].b") byte[] extractedPayload = new FieldPathPayloadSubsectionExtractor("a.[].b")
.extractSubsection("{\"a\":[{\"b\":{\"c\":5}},{\"b\":{\"c\":6}}]}".getBytes(), MediaType.APPLICATION_JSON); .extractSubsection("{\"a\":[{\"b\":{\"c\":5}},{\"b\":{\"c\":6}}]}".getBytes(), MediaType.APPLICATION_JSON);
@@ -95,7 +93,7 @@ public class FieldPathPayloadSubsectionExtractorTests {
} }
@Test @Test
public void extractMapSubsectionWithVaryingStructureFromMultiElementArrayInAJsonMap() { void extractMapSubsectionWithVaryingStructureFromMultiElementArrayInAJsonMap() {
assertThatExceptionOfType(PayloadHandlingException.class) assertThatExceptionOfType(PayloadHandlingException.class)
.isThrownBy(() -> new FieldPathPayloadSubsectionExtractor("a.[].b").extractSubsection( .isThrownBy(() -> new FieldPathPayloadSubsectionExtractor("a.[].b").extractSubsection(
"{\"a\":[{\"b\":{\"c\":5}},{\"b\":{\"c\":6, \"d\": 7}}]}".getBytes(), MediaType.APPLICATION_JSON)) "{\"a\":[{\"b\":{\"c\":5}},{\"b\":{\"c\":6, \"d\": 7}}]}".getBytes(), MediaType.APPLICATION_JSON))
@@ -103,7 +101,7 @@ public class FieldPathPayloadSubsectionExtractorTests {
} }
@Test @Test
public void extractMapSubsectionWithVaryingStructureFromInconsistentJsonMap() { void extractMapSubsectionWithVaryingStructureFromInconsistentJsonMap() {
assertThatExceptionOfType(PayloadHandlingException.class) assertThatExceptionOfType(PayloadHandlingException.class)
.isThrownBy(() -> new FieldPathPayloadSubsectionExtractor("*.d").extractSubsection( .isThrownBy(() -> new FieldPathPayloadSubsectionExtractor("*.d").extractSubsection(
"{\"a\":{\"b\":1},\"c\":{\"d\":{\"e\":1,\"f\":2}}}".getBytes(), MediaType.APPLICATION_JSON)) "{\"a\":{\"b\":1},\"c\":{\"d\":{\"e\":1,\"f\":2}}}".getBytes(), MediaType.APPLICATION_JSON))
@@ -111,7 +109,7 @@ public class FieldPathPayloadSubsectionExtractorTests {
} }
@Test @Test
public void extractMapSubsectionWithVaryingStructureFromInconsistentJsonMapWhereAllSubsectionFieldsAreOptional() { void extractMapSubsectionWithVaryingStructureFromInconsistentJsonMapWhereAllSubsectionFieldsAreOptional() {
assertThatExceptionOfType(PayloadHandlingException.class) assertThatExceptionOfType(PayloadHandlingException.class)
.isThrownBy(() -> new FieldPathPayloadSubsectionExtractor("*.d").extractSubsection( .isThrownBy(() -> new FieldPathPayloadSubsectionExtractor("*.d").extractSubsection(
"{\"a\":{\"b\":1},\"c\":{\"d\":{\"e\":1,\"f\":2}}}".getBytes(), MediaType.APPLICATION_JSON, "{\"a\":{\"b\":1},\"c\":{\"d\":{\"e\":1,\"f\":2}}}".getBytes(), MediaType.APPLICATION_JSON,
@@ -121,7 +119,7 @@ public class FieldPathPayloadSubsectionExtractorTests {
@Test @Test
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public void extractMapSubsectionWithVaryingStructureDueToOptionalFieldsFromMultiElementArrayInAJsonMap() void extractMapSubsectionWithVaryingStructureDueToOptionalFieldsFromMultiElementArrayInAJsonMap()
throws JsonParseException, JsonMappingException, IOException { throws JsonParseException, JsonMappingException, IOException {
byte[] extractedPayload = new FieldPathPayloadSubsectionExtractor("a.[].b").extractSubsection( byte[] extractedPayload = new FieldPathPayloadSubsectionExtractor("a.[].b").extractSubsection(
"{\"a\":[{\"b\":{\"c\":5}},{\"b\":{\"c\":6, \"d\": 7}}]}".getBytes(), MediaType.APPLICATION_JSON, "{\"a\":[{\"b\":{\"c\":5}},{\"b\":{\"c\":6, \"d\": 7}}]}".getBytes(), MediaType.APPLICATION_JSON,
@@ -133,7 +131,7 @@ public class FieldPathPayloadSubsectionExtractorTests {
@Test @Test
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public void extractMapSubsectionWithVaryingStructureDueToOptionalParentFieldsFromMultiElementArrayInAJsonMap() void extractMapSubsectionWithVaryingStructureDueToOptionalParentFieldsFromMultiElementArrayInAJsonMap()
throws JsonParseException, JsonMappingException, IOException { throws JsonParseException, JsonMappingException, IOException {
byte[] extractedPayload = new FieldPathPayloadSubsectionExtractor("a.[].b").extractSubsection( byte[] extractedPayload = new FieldPathPayloadSubsectionExtractor("a.[].b").extractSubsection(
"{\"a\":[{\"b\":{\"c\":5}},{\"b\":{\"c\":6, \"d\": { \"e\": 7}}}]}".getBytes(), "{\"a\":[{\"b\":{\"c\":5}},{\"b\":{\"c\":6, \"d\": { \"e\": 7}}}]}".getBytes(),
@@ -144,7 +142,7 @@ public class FieldPathPayloadSubsectionExtractorTests {
} }
@Test @Test
public void extractedSubsectionIsPrettyPrintedWhenInputIsPrettyPrinted() void extractedSubsectionIsPrettyPrintedWhenInputIsPrettyPrinted()
throws JsonParseException, JsonMappingException, JsonProcessingException, IOException { throws JsonParseException, JsonMappingException, JsonProcessingException, IOException {
ObjectMapper objectMapper = new ObjectMapper().enable(SerializationFeature.INDENT_OUTPUT); ObjectMapper objectMapper = new ObjectMapper().enable(SerializationFeature.INDENT_OUTPUT);
byte[] prettyPrintedPayload = objectMapper byte[] prettyPrintedPayload = objectMapper
@@ -157,7 +155,7 @@ public class FieldPathPayloadSubsectionExtractorTests {
} }
@Test @Test
public void extractedSubsectionIsNotPrettyPrintedWhenInputIsNotPrettyPrinted() void extractedSubsectionIsNotPrettyPrintedWhenInputIsNotPrettyPrinted()
throws JsonParseException, JsonMappingException, JsonProcessingException, IOException { throws JsonParseException, JsonMappingException, JsonProcessingException, IOException {
ObjectMapper objectMapper = new ObjectMapper(); ObjectMapper objectMapper = new ObjectMapper();
byte[] payload = objectMapper byte[] payload = objectMapper
@@ -169,7 +167,7 @@ public class FieldPathPayloadSubsectionExtractorTests {
} }
@Test @Test
public void extractNonExistentSubsection() { void extractNonExistentSubsection() {
assertThatThrownBy(() -> new FieldPathPayloadSubsectionExtractor("a.c") assertThatThrownBy(() -> new FieldPathPayloadSubsectionExtractor("a.c")
.extractSubsection("{\"a\":{\"b\":{\"c\":5}}}".getBytes(), MediaType.APPLICATION_JSON)) .extractSubsection("{\"a\":{\"b\":{\"c\":5}}}".getBytes(), MediaType.APPLICATION_JSON))
.isInstanceOf(PayloadHandlingException.class) .isInstanceOf(PayloadHandlingException.class)
@@ -177,7 +175,7 @@ public class FieldPathPayloadSubsectionExtractorTests {
} }
@Test @Test
public void extractEmptyArraySubsection() { void extractEmptyArraySubsection() {
assertThatThrownBy(() -> new FieldPathPayloadSubsectionExtractor("a") assertThatThrownBy(() -> new FieldPathPayloadSubsectionExtractor("a")
.extractSubsection("{\"a\":[]}}".getBytes(), MediaType.APPLICATION_JSON)) .extractSubsection("{\"a\":[]}}".getBytes(), MediaType.APPLICATION_JSON))
.isInstanceOf(PayloadHandlingException.class) .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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with 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 java.util.Collections;
import org.junit.Test; import org.junit.jupiter.api.Test;
import org.springframework.http.MediaType; 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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with 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.Collections;
import java.util.List; 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.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
@@ -31,10 +31,10 @@ import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
* @author Andy Wilkinson * @author Andy Wilkinson
* @author Mathias Düsterhöft * @author Mathias Düsterhöft
*/ */
public class JsonContentHandlerTests { class JsonContentHandlerTests {
@Test @Test
public void typeForFieldWithNullValueMustMatch() { void typeForFieldWithNullValueMustMatch() {
FieldDescriptor descriptor = new FieldDescriptor("a").type(JsonFieldType.STRING); FieldDescriptor descriptor = new FieldDescriptor("a").type(JsonFieldType.STRING);
assertThatExceptionOfType(FieldTypesDoNotMatchException.class) assertThatExceptionOfType(FieldTypesDoNotMatchException.class)
.isThrownBy(() -> new JsonContentHandler("{\"a\": null}".getBytes(), Arrays.asList(descriptor)) .isThrownBy(() -> new JsonContentHandler("{\"a\": null}".getBytes(), Arrays.asList(descriptor))
@@ -42,7 +42,7 @@ public class JsonContentHandlerTests {
} }
@Test @Test
public void typeForFieldWithNotNullAndThenNullValueMustMatch() { void typeForFieldWithNotNullAndThenNullValueMustMatch() {
FieldDescriptor descriptor = new FieldDescriptor("a[].id").type(JsonFieldType.STRING); FieldDescriptor descriptor = new FieldDescriptor("a[].id").type(JsonFieldType.STRING);
assertThatExceptionOfType(FieldTypesDoNotMatchException.class).isThrownBy( assertThatExceptionOfType(FieldTypesDoNotMatchException.class).isThrownBy(
() -> new JsonContentHandler("{\"a\":[{\"id\":1},{\"id\":null}]}".getBytes(), Arrays.asList(descriptor)) () -> new JsonContentHandler("{\"a\":[{\"id\":1},{\"id\":null}]}".getBytes(), Arrays.asList(descriptor))
@@ -50,7 +50,7 @@ public class JsonContentHandlerTests {
} }
@Test @Test
public void typeForFieldWithNullAndThenNotNullValueMustMatch() { void typeForFieldWithNullAndThenNotNullValueMustMatch() {
FieldDescriptor descriptor = new FieldDescriptor("a.[].id").type(JsonFieldType.STRING); FieldDescriptor descriptor = new FieldDescriptor("a.[].id").type(JsonFieldType.STRING);
assertThatExceptionOfType(FieldTypesDoNotMatchException.class).isThrownBy( assertThatExceptionOfType(FieldTypesDoNotMatchException.class).isThrownBy(
() -> new JsonContentHandler("{\"a\":[{\"id\":null},{\"id\":1}]}".getBytes(), Arrays.asList(descriptor)) () -> new JsonContentHandler("{\"a\":[{\"id\":null},{\"id\":1}]}".getBytes(), Arrays.asList(descriptor))
@@ -58,7 +58,7 @@ public class JsonContentHandlerTests {
} }
@Test @Test
public void typeForOptionalFieldWithNumberAndThenNullValueIsNumber() { void typeForOptionalFieldWithNumberAndThenNullValueIsNumber() {
FieldDescriptor descriptor = new FieldDescriptor("a[].id").optional(); FieldDescriptor descriptor = new FieldDescriptor("a[].id").optional();
Object fieldType = new JsonContentHandler("{\"a\":[{\"id\":1},{\"id\":null}]}\"".getBytes(), Object fieldType = new JsonContentHandler("{\"a\":[{\"id\":1},{\"id\":null}]}\"".getBytes(),
Arrays.asList(descriptor)) Arrays.asList(descriptor))
@@ -67,7 +67,7 @@ public class JsonContentHandlerTests {
} }
@Test @Test
public void typeForOptionalFieldWithNullAndThenNumberIsNumber() { void typeForOptionalFieldWithNullAndThenNumberIsNumber() {
FieldDescriptor descriptor = new FieldDescriptor("a[].id").optional(); FieldDescriptor descriptor = new FieldDescriptor("a[].id").optional();
Object fieldType = new JsonContentHandler("{\"a\":[{\"id\":null},{\"id\":1}]}".getBytes(), Object fieldType = new JsonContentHandler("{\"a\":[{\"id\":null},{\"id\":1}]}".getBytes(),
Arrays.asList(descriptor)) Arrays.asList(descriptor))
@@ -76,7 +76,7 @@ public class JsonContentHandlerTests {
} }
@Test @Test
public void typeForFieldWithNumberAndThenNullValueIsVaries() { void typeForFieldWithNumberAndThenNullValueIsVaries() {
FieldDescriptor descriptor = new FieldDescriptor("a[].id"); FieldDescriptor descriptor = new FieldDescriptor("a[].id");
Object fieldType = new JsonContentHandler("{\"a\":[{\"id\":1},{\"id\":null}]}\"".getBytes(), Object fieldType = new JsonContentHandler("{\"a\":[{\"id\":1},{\"id\":null}]}\"".getBytes(),
Arrays.asList(descriptor)) Arrays.asList(descriptor))
@@ -85,7 +85,7 @@ public class JsonContentHandlerTests {
} }
@Test @Test
public void typeForFieldWithNullAndThenNumberIsVaries() { void typeForFieldWithNullAndThenNumberIsVaries() {
FieldDescriptor descriptor = new FieldDescriptor("a[].id"); FieldDescriptor descriptor = new FieldDescriptor("a[].id");
Object fieldType = new JsonContentHandler("{\"a\":[{\"id\":null},{\"id\":1}]}".getBytes(), Object fieldType = new JsonContentHandler("{\"a\":[{\"id\":null},{\"id\":1}]}".getBytes(),
Arrays.asList(descriptor)) Arrays.asList(descriptor))
@@ -94,7 +94,7 @@ public class JsonContentHandlerTests {
} }
@Test @Test
public void typeForOptionalFieldWithNullValueCanBeProvidedExplicitly() { void typeForOptionalFieldWithNullValueCanBeProvidedExplicitly() {
FieldDescriptor descriptor = new FieldDescriptor("a").type(JsonFieldType.STRING).optional(); FieldDescriptor descriptor = new FieldDescriptor("a").type(JsonFieldType.STRING).optional();
Object fieldType = new JsonContentHandler("{\"a\": null}".getBytes(), Arrays.asList(descriptor)) Object fieldType = new JsonContentHandler("{\"a\": null}".getBytes(), Arrays.asList(descriptor))
.resolveFieldType(descriptor); .resolveFieldType(descriptor);
@@ -102,7 +102,7 @@ public class JsonContentHandlerTests {
} }
@Test @Test
public void typeForFieldWithSometimesPresentOptionalAncestorCanBeProvidedExplicitly() { void typeForFieldWithSometimesPresentOptionalAncestorCanBeProvidedExplicitly() {
FieldDescriptor descriptor = new FieldDescriptor("a.[].b.c").type(JsonFieldType.NUMBER); FieldDescriptor descriptor = new FieldDescriptor("a.[].b.c").type(JsonFieldType.NUMBER);
FieldDescriptor ancestor = new FieldDescriptor("a.[].b").optional(); FieldDescriptor ancestor = new FieldDescriptor("a.[].b").optional();
Object fieldType = new JsonContentHandler("{\"a\":[ { \"d\": 4}, {\"b\":{\"c\":5}, \"d\": 4}]}".getBytes(), Object fieldType = new JsonContentHandler("{\"a\":[ { \"d\": 4}, {\"b\":{\"c\":5}, \"d\": 4}]}".getBytes(),
@@ -112,13 +112,13 @@ public class JsonContentHandlerTests {
} }
@Test @Test
public void failsFastWithNonJsonContent() { void failsFastWithNonJsonContent() {
assertThatExceptionOfType(PayloadHandlingException.class) assertThatExceptionOfType(PayloadHandlingException.class)
.isThrownBy(() -> new JsonContentHandler("Non-JSON content".getBytes(), Collections.emptyList())); .isThrownBy(() -> new JsonContentHandler("Non-JSON content".getBytes(), Collections.emptyList()));
} }
@Test @Test
public void describedFieldThatIsNotPresentIsConsideredMissing() { void describedFieldThatIsNotPresentIsConsideredMissing() {
List<FieldDescriptor> descriptors = Arrays.asList(new FieldDescriptor("a"), new FieldDescriptor("b"), List<FieldDescriptor> descriptors = Arrays.asList(new FieldDescriptor("a"), new FieldDescriptor("b"),
new FieldDescriptor("c")); new FieldDescriptor("c"));
List<FieldDescriptor> missingFields = new JsonContentHandler("{\"a\": \"alpha\", \"b\":\"bravo\"}".getBytes(), List<FieldDescriptor> missingFields = new JsonContentHandler("{\"a\": \"alpha\", \"b\":\"bravo\"}".getBytes(),
@@ -129,7 +129,7 @@ public class JsonContentHandlerTests {
} }
@Test @Test
public void describedOptionalFieldThatIsNotPresentIsNotConsideredMissing() { void describedOptionalFieldThatIsNotPresentIsNotConsideredMissing() {
List<FieldDescriptor> descriptors = Arrays.asList(new FieldDescriptor("a"), new FieldDescriptor("b"), List<FieldDescriptor> descriptors = Arrays.asList(new FieldDescriptor("a"), new FieldDescriptor("b"),
new FieldDescriptor("c").optional()); new FieldDescriptor("c").optional());
List<FieldDescriptor> missingFields = new JsonContentHandler("{\"a\": \"alpha\", \"b\":\"bravo\"}".getBytes(), List<FieldDescriptor> missingFields = new JsonContentHandler("{\"a\": \"alpha\", \"b\":\"bravo\"}".getBytes(),
@@ -139,7 +139,7 @@ public class JsonContentHandlerTests {
} }
@Test @Test
public void describedFieldThatIsNotPresentNestedBeneathOptionalFieldThatIsPresentIsConsideredMissing() { void describedFieldThatIsNotPresentNestedBeneathOptionalFieldThatIsPresentIsConsideredMissing() {
List<FieldDescriptor> descriptors = Arrays.asList(new FieldDescriptor("a").optional(), new FieldDescriptor("b"), List<FieldDescriptor> descriptors = Arrays.asList(new FieldDescriptor("a").optional(), new FieldDescriptor("b"),
new FieldDescriptor("a.c")); new FieldDescriptor("a.c"));
List<FieldDescriptor> missingFields = new JsonContentHandler("{\"a\":\"alpha\",\"b\":\"bravo\"}".getBytes(), List<FieldDescriptor> missingFields = new JsonContentHandler("{\"a\":\"alpha\",\"b\":\"bravo\"}".getBytes(),
@@ -150,7 +150,7 @@ public class JsonContentHandlerTests {
} }
@Test @Test
public void describedFieldThatIsNotPresentNestedBeneathOptionalFieldThatIsNotPresentIsNotConsideredMissing() { void describedFieldThatIsNotPresentNestedBeneathOptionalFieldThatIsNotPresentIsNotConsideredMissing() {
List<FieldDescriptor> descriptors = Arrays.asList(new FieldDescriptor("a").optional(), new FieldDescriptor("b"), List<FieldDescriptor> descriptors = Arrays.asList(new FieldDescriptor("a").optional(), new FieldDescriptor("b"),
new FieldDescriptor("a.c")); new FieldDescriptor("a.c"));
List<FieldDescriptor> missingFields = new JsonContentHandler("{\"b\":\"bravo\"}".getBytes(), descriptors) List<FieldDescriptor> missingFields = new JsonContentHandler("{\"b\":\"bravo\"}".getBytes(), descriptors)
@@ -159,7 +159,7 @@ public class JsonContentHandlerTests {
} }
@Test @Test
public void describedFieldThatIsNotPresentNestedBeneathOptionalArrayThatIsEmptyIsNotConsideredMissing() { void describedFieldThatIsNotPresentNestedBeneathOptionalArrayThatIsEmptyIsNotConsideredMissing() {
List<FieldDescriptor> descriptors = Arrays.asList(new FieldDescriptor("outer"), List<FieldDescriptor> descriptors = Arrays.asList(new FieldDescriptor("outer"),
new FieldDescriptor("outer[]").optional(), new FieldDescriptor("outer[].inner")); new FieldDescriptor("outer[]").optional(), new FieldDescriptor("outer[].inner"));
List<FieldDescriptor> missingFields = new JsonContentHandler("{\"outer\":[]}".getBytes(), descriptors) List<FieldDescriptor> missingFields = new JsonContentHandler("{\"outer\":[]}".getBytes(), descriptors)
@@ -168,7 +168,7 @@ public class JsonContentHandlerTests {
} }
@Test @Test
public void describedSometimesPresentFieldThatIsChildOfSometimesPresentOptionalArrayIsNotConsideredMissing() { void describedSometimesPresentFieldThatIsChildOfSometimesPresentOptionalArrayIsNotConsideredMissing() {
List<FieldDescriptor> descriptors = Arrays.asList(new FieldDescriptor("a.[].c").optional(), List<FieldDescriptor> descriptors = Arrays.asList(new FieldDescriptor("a.[].c").optional(),
new FieldDescriptor("a.[].c.d")); new FieldDescriptor("a.[].c.d"));
List<FieldDescriptor> missingFields = new JsonContentHandler( List<FieldDescriptor> missingFields = new JsonContentHandler(
@@ -178,7 +178,7 @@ public class JsonContentHandlerTests {
} }
@Test @Test
public void describedMissingFieldThatIsChildOfNestedOptionalArrayThatIsEmptyIsNotConsideredMissing() { void describedMissingFieldThatIsChildOfNestedOptionalArrayThatIsEmptyIsNotConsideredMissing() {
List<FieldDescriptor> descriptors = Arrays.asList(new FieldDescriptor("a.[].b").optional(), List<FieldDescriptor> descriptors = Arrays.asList(new FieldDescriptor("a.[].b").optional(),
new FieldDescriptor("a.[].b.[]").optional(), new FieldDescriptor("a.[].b.[].c")); new FieldDescriptor("a.[].b.[]").optional(), new FieldDescriptor("a.[].b.[].c"));
List<FieldDescriptor> missingFields = new JsonContentHandler("{\"a\":[{\"b\":[]}]}".getBytes(), descriptors) List<FieldDescriptor> missingFields = new JsonContentHandler("{\"a\":[{\"b\":[]}]}".getBytes(), descriptors)
@@ -187,7 +187,7 @@ public class JsonContentHandlerTests {
} }
@Test @Test
public void describedMissingFieldThatIsChildOfNestedOptionalArrayThatContainsAnObjectIsConsideredMissing() { void describedMissingFieldThatIsChildOfNestedOptionalArrayThatContainsAnObjectIsConsideredMissing() {
List<FieldDescriptor> descriptors = Arrays.asList(new FieldDescriptor("a.[].b").optional(), List<FieldDescriptor> descriptors = Arrays.asList(new FieldDescriptor("a.[].b").optional(),
new FieldDescriptor("a.[].b.[]").optional(), new FieldDescriptor("a.[].b.[].c")); new FieldDescriptor("a.[].b.[]").optional(), new FieldDescriptor("a.[].b.[].c"));
List<FieldDescriptor> missingFields = new JsonContentHandler("{\"a\":[{\"b\":[{}]}]}".getBytes(), descriptors) List<FieldDescriptor> missingFields = new JsonContentHandler("{\"a\":[{\"b\":[{}]}]}".getBytes(), descriptors)
@@ -197,7 +197,7 @@ public class JsonContentHandlerTests {
} }
@Test @Test
public void describedMissingFieldThatIsChildOfOptionalObjectThatIsNullIsNotConsideredMissing() { void describedMissingFieldThatIsChildOfOptionalObjectThatIsNullIsNotConsideredMissing() {
List<FieldDescriptor> descriptors = Arrays.asList(new FieldDescriptor("a").optional(), List<FieldDescriptor> descriptors = Arrays.asList(new FieldDescriptor("a").optional(),
new FieldDescriptor("a.b")); new FieldDescriptor("a.b"));
List<FieldDescriptor> missingFields = new JsonContentHandler("{\"a\":null}".getBytes(), descriptors) 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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -16,7 +16,7 @@
package org.springframework.restdocs.payload; package org.springframework.restdocs.payload;
import org.junit.Test; import org.junit.jupiter.api.Test;
import org.springframework.restdocs.payload.JsonFieldPath.PathType; import org.springframework.restdocs.payload.JsonFieldPath.PathType;
@@ -28,131 +28,131 @@ import static org.assertj.core.api.Assertions.assertThat;
* @author Andy Wilkinson * @author Andy Wilkinson
* @author Jeremy Rickard * @author Jeremy Rickard
*/ */
public class JsonFieldPathTests { class JsonFieldPathTests {
@Test @Test
public void pathTypeOfSingleFieldIsSingle() { void pathTypeOfSingleFieldIsSingle() {
JsonFieldPath path = JsonFieldPath.compile("a"); JsonFieldPath path = JsonFieldPath.compile("a");
assertThat(path.getType()).isEqualTo(PathType.SINGLE); assertThat(path.getType()).isEqualTo(PathType.SINGLE);
} }
@Test @Test
public void pathTypeOfSingleNestedFieldIsSingle() { void pathTypeOfSingleNestedFieldIsSingle() {
JsonFieldPath path = JsonFieldPath.compile("a.b"); JsonFieldPath path = JsonFieldPath.compile("a.b");
assertThat(path.getType()).isEqualTo(PathType.SINGLE); assertThat(path.getType()).isEqualTo(PathType.SINGLE);
} }
@Test @Test
public void pathTypeOfTopLevelArrayIsSingle() { void pathTypeOfTopLevelArrayIsSingle() {
JsonFieldPath path = JsonFieldPath.compile("[]"); JsonFieldPath path = JsonFieldPath.compile("[]");
assertThat(path.getType()).isEqualTo(PathType.SINGLE); assertThat(path.getType()).isEqualTo(PathType.SINGLE);
} }
@Test @Test
public void pathTypeOfFieldBeneathTopLevelArrayIsMulti() { void pathTypeOfFieldBeneathTopLevelArrayIsMulti() {
JsonFieldPath path = JsonFieldPath.compile("[]a"); JsonFieldPath path = JsonFieldPath.compile("[]a");
assertThat(path.getType()).isEqualTo(PathType.MULTI); assertThat(path.getType()).isEqualTo(PathType.MULTI);
} }
@Test @Test
public void pathTypeOfSingleNestedArrayIsSingle() { void pathTypeOfSingleNestedArrayIsSingle() {
JsonFieldPath path = JsonFieldPath.compile("a[]"); JsonFieldPath path = JsonFieldPath.compile("a[]");
assertThat(path.getType()).isEqualTo(PathType.SINGLE); assertThat(path.getType()).isEqualTo(PathType.SINGLE);
} }
@Test @Test
public void pathTypeOfArrayBeneathNestedFieldsIsSingle() { void pathTypeOfArrayBeneathNestedFieldsIsSingle() {
JsonFieldPath path = JsonFieldPath.compile("a.b[]"); JsonFieldPath path = JsonFieldPath.compile("a.b[]");
assertThat(path.getType()).isEqualTo(PathType.SINGLE); assertThat(path.getType()).isEqualTo(PathType.SINGLE);
} }
@Test @Test
public void pathTypeOfArrayOfArraysIsMulti() { void pathTypeOfArrayOfArraysIsMulti() {
JsonFieldPath path = JsonFieldPath.compile("a[][]"); JsonFieldPath path = JsonFieldPath.compile("a[][]");
assertThat(path.getType()).isEqualTo(PathType.MULTI); assertThat(path.getType()).isEqualTo(PathType.MULTI);
} }
@Test @Test
public void pathTypeOfFieldBeneathAnArrayIsMulti() { void pathTypeOfFieldBeneathAnArrayIsMulti() {
JsonFieldPath path = JsonFieldPath.compile("a[].b"); JsonFieldPath path = JsonFieldPath.compile("a[].b");
assertThat(path.getType()).isEqualTo(PathType.MULTI); assertThat(path.getType()).isEqualTo(PathType.MULTI);
} }
@Test @Test
public void pathTypeOfFieldBeneathTopLevelWildcardIsMulti() { void pathTypeOfFieldBeneathTopLevelWildcardIsMulti() {
JsonFieldPath path = JsonFieldPath.compile("*.a"); JsonFieldPath path = JsonFieldPath.compile("*.a");
assertThat(path.getType()).isEqualTo(PathType.MULTI); assertThat(path.getType()).isEqualTo(PathType.MULTI);
} }
@Test @Test
public void pathTypeOfFieldBeneathNestedWildcardIsMulti() { void pathTypeOfFieldBeneathNestedWildcardIsMulti() {
JsonFieldPath path = JsonFieldPath.compile("a.*.b"); JsonFieldPath path = JsonFieldPath.compile("a.*.b");
assertThat(path.getType()).isEqualTo(PathType.MULTI); assertThat(path.getType()).isEqualTo(PathType.MULTI);
} }
@Test @Test
public void pathTypeOfLeafWidlcardIsMulti() { void pathTypeOfLeafWidlcardIsMulti() {
JsonFieldPath path = JsonFieldPath.compile("a.*"); JsonFieldPath path = JsonFieldPath.compile("a.*");
assertThat(path.getType()).isEqualTo(PathType.MULTI); assertThat(path.getType()).isEqualTo(PathType.MULTI);
} }
@Test @Test
public void compilationOfSingleElementPath() { void compilationOfSingleElementPath() {
assertThat(JsonFieldPath.compile("a").getSegments()).containsExactly("a"); assertThat(JsonFieldPath.compile("a").getSegments()).containsExactly("a");
} }
@Test @Test
public void compilationOfMultipleElementPath() { void compilationOfMultipleElementPath() {
assertThat(JsonFieldPath.compile("a.b.c").getSegments()).containsExactly("a", "b", "c"); assertThat(JsonFieldPath.compile("a.b.c").getSegments()).containsExactly("a", "b", "c");
} }
@Test @Test
public void compilationOfPathWithArraysWithNoDotSeparators() { void compilationOfPathWithArraysWithNoDotSeparators() {
assertThat(JsonFieldPath.compile("a[]b[]c").getSegments()).containsExactly("a", "[]", "b", "[]", "c"); assertThat(JsonFieldPath.compile("a[]b[]c").getSegments()).containsExactly("a", "[]", "b", "[]", "c");
} }
@Test @Test
public void compilationOfPathWithArraysWithPreAndPostDotSeparators() { void compilationOfPathWithArraysWithPreAndPostDotSeparators() {
assertThat(JsonFieldPath.compile("a.[].b.[].c").getSegments()).containsExactly("a", "[]", "b", "[]", "c"); assertThat(JsonFieldPath.compile("a.[].b.[].c").getSegments()).containsExactly("a", "[]", "b", "[]", "c");
} }
@Test @Test
public void compilationOfPathWithArraysWithPreDotSeparators() { void compilationOfPathWithArraysWithPreDotSeparators() {
assertThat(JsonFieldPath.compile("a.[]b.[]c").getSegments()).containsExactly("a", "[]", "b", "[]", "c"); assertThat(JsonFieldPath.compile("a.[]b.[]c").getSegments()).containsExactly("a", "[]", "b", "[]", "c");
} }
@Test @Test
public void compilationOfPathWithArraysWithPostDotSeparators() { void compilationOfPathWithArraysWithPostDotSeparators() {
assertThat(JsonFieldPath.compile("a[].b[].c").getSegments()).containsExactly("a", "[]", "b", "[]", "c"); assertThat(JsonFieldPath.compile("a[].b[].c").getSegments()).containsExactly("a", "[]", "b", "[]", "c");
} }
@Test @Test
public void compilationOfPathStartingWithAnArray() { void compilationOfPathStartingWithAnArray() {
assertThat(JsonFieldPath.compile("[]a.b.c").getSegments()).containsExactly("[]", "a", "b", "c"); assertThat(JsonFieldPath.compile("[]a.b.c").getSegments()).containsExactly("[]", "a", "b", "c");
} }
@Test @Test
public void compilationOfMultipleElementPathWithBrackets() { void compilationOfMultipleElementPathWithBrackets() {
assertThat(JsonFieldPath.compile("['a']['b']['c']").getSegments()).containsExactly("a", "b", "c"); assertThat(JsonFieldPath.compile("['a']['b']['c']").getSegments()).containsExactly("a", "b", "c");
} }
@Test @Test
public void compilationOfMultipleElementPathWithAndWithoutBrackets() { void compilationOfMultipleElementPathWithAndWithoutBrackets() {
assertThat(JsonFieldPath.compile("['a'][].b['c']").getSegments()).containsExactly("a", "[]", "b", "c"); assertThat(JsonFieldPath.compile("['a'][].b['c']").getSegments()).containsExactly("a", "[]", "b", "c");
} }
@Test @Test
public void compilationOfMultipleElementPathWithAndWithoutBracketsAndEmbeddedDots() { void compilationOfMultipleElementPathWithAndWithoutBracketsAndEmbeddedDots() {
assertThat(JsonFieldPath.compile("['a.key'][].b['c']").getSegments()).containsExactly("a.key", "[]", "b", "c"); assertThat(JsonFieldPath.compile("['a.key'][].b['c']").getSegments()).containsExactly("a.key", "[]", "b", "c");
} }
@Test @Test
public void compilationOfPathWithAWildcard() { void compilationOfPathWithAWildcard() {
assertThat(JsonFieldPath.compile("a.b.*.c").getSegments()).containsExactly("a", "b", "*", "c"); assertThat(JsonFieldPath.compile("a.b.*.c").getSegments()).containsExactly("a", "b", "*", "c");
} }
@Test @Test
public void compilationOfPathWithAWildcardInBrackets() { void compilationOfPathWithAWildcardInBrackets() {
assertThat(JsonFieldPath.compile("a.b.['*'].c").getSegments()).containsExactly("a", "b", "*", "c"); 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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with 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 java.util.Arrays;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.Test; import org.junit.jupiter.api.Test;
import org.springframework.restdocs.payload.JsonFieldProcessor.ExtractedField; import org.springframework.restdocs.payload.JsonFieldProcessor.ExtractedField;
@@ -31,10 +31,10 @@ import static org.assertj.core.api.Assertions.assertThat;
* *
* @author Andy Wilkinson * @author Andy Wilkinson
*/ */
public class JsonFieldPathsTests { class JsonFieldPathsTests {
@Test @Test
public void noUncommonPathsForSingleItem() { void noUncommonPathsForSingleItem() {
assertThat( assertThat(
JsonFieldPaths.from(Arrays.asList(json("{\"a\": 1, \"b\": [ { \"c\": 2}, {\"c\": 3}, {\"c\": null}]}"))) JsonFieldPaths.from(Arrays.asList(json("{\"a\": 1, \"b\": [ { \"c\": 2}, {\"c\": 3}, {\"c\": null}]}")))
.getUncommon()) .getUncommon())
@@ -42,20 +42,20 @@ public class JsonFieldPathsTests {
} }
@Test @Test
public void noUncommonPathsForMultipleIdenticalItems() { void noUncommonPathsForMultipleIdenticalItems() {
Object item = json("{\"a\": 1, \"b\": [ { \"c\": 2}, {\"c\": 3} ]}"); Object item = json("{\"a\": 1, \"b\": [ { \"c\": 2}, {\"c\": 3} ]}");
assertThat(JsonFieldPaths.from(Arrays.asList(item, item)).getUncommon()).isEmpty(); assertThat(JsonFieldPaths.from(Arrays.asList(item, item)).getUncommon()).isEmpty();
} }
@Test @Test
public void noUncommonPathsForMultipleMatchingItemsWithDifferentScalarValues() { void noUncommonPathsForMultipleMatchingItemsWithDifferentScalarValues() {
assertThat(JsonFieldPaths.from(Arrays.asList(json("{\"a\": 1, \"b\": [ { \"c\": 2}, {\"c\": 3} ]}"), assertThat(JsonFieldPaths.from(Arrays.asList(json("{\"a\": 1, \"b\": [ { \"c\": 2}, {\"c\": 3} ]}"),
json("{\"a\": 4, \"b\": [ { \"c\": 5}, {\"c\": 6} ]}"))) json("{\"a\": 4, \"b\": [ { \"c\": 5}, {\"c\": 6} ]}")))
.getUncommon()).isEmpty(); .getUncommon()).isEmpty();
} }
@Test @Test
public void missingEntryInMapIsIdentifiedAsUncommon() { void missingEntryInMapIsIdentifiedAsUncommon() {
assertThat( assertThat(
JsonFieldPaths.from(Arrays.asList(json("{\"a\": 1}"), json("{\"a\": 1}"), json("{\"a\": 1, \"b\": 2}"))) JsonFieldPaths.from(Arrays.asList(json("{\"a\": 1}"), json("{\"a\": 1}"), json("{\"a\": 1, \"b\": 2}")))
.getUncommon()) .getUncommon())
@@ -63,21 +63,21 @@ public class JsonFieldPathsTests {
} }
@Test @Test
public void missingEntryInNestedMapIsIdentifiedAsUncommon() { void missingEntryInNestedMapIsIdentifiedAsUncommon() {
assertThat(JsonFieldPaths.from(Arrays.asList(json("{\"a\": 1, \"b\": {\"c\": 1}}"), 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}}"))) json("{\"a\": 1, \"b\": {\"c\": 1}}"), json("{\"a\": 1, \"b\": {\"c\": 1, \"d\": 2}}")))
.getUncommon()).containsExactly("b.d"); .getUncommon()).containsExactly("b.d");
} }
@Test @Test
public void missingEntriesInNestedMapAreIdentifiedAsUncommon() { void missingEntriesInNestedMapAreIdentifiedAsUncommon() {
assertThat(JsonFieldPaths.from(Arrays.asList(json("{\"a\": 1, \"b\": {\"c\": 1}}"), assertThat(JsonFieldPaths.from(Arrays.asList(json("{\"a\": 1, \"b\": {\"c\": 1}}"),
json("{\"a\": 1, \"b\": {\"c\": 1}}"), json("{\"a\": 1, \"b\": {\"d\": 2}}"))) json("{\"a\": 1, \"b\": {\"c\": 1}}"), json("{\"a\": 1, \"b\": {\"d\": 2}}")))
.getUncommon()).containsExactly("b.c", "b.d"); .getUncommon()).containsExactly("b.c", "b.d");
} }
@Test @Test
public void absentItemFromFieldExtractionCausesAllPresentFieldsToBeIdentifiedAsUncommon() { void absentItemFromFieldExtractionCausesAllPresentFieldsToBeIdentifiedAsUncommon() {
assertThat(JsonFieldPaths assertThat(JsonFieldPaths
.from(Arrays.asList(ExtractedField.ABSENT, json("{\"a\": 1, \"b\": {\"c\": 1}}"), .from(Arrays.asList(ExtractedField.ABSENT, json("{\"a\": 1, \"b\": {\"c\": 1}}"),
json("{\"a\": 1, \"b\": {\"c\": 1}}"), json("{\"a\": 1, \"b\": {\"d\": 2}}"))) json("{\"a\": 1, \"b\": {\"c\": 1}}"), json("{\"a\": 1, \"b\": {\"d\": 2}}")))
@@ -85,14 +85,14 @@ public class JsonFieldPathsTests {
} }
@Test @Test
public void missingEntryBeneathArrayIsIdentifiedAsUncommon() { void missingEntryBeneathArrayIsIdentifiedAsUncommon() {
assertThat(JsonFieldPaths assertThat(JsonFieldPaths
.from(Arrays.asList(json("[{\"b\": 1}]"), json("[{\"b\": 1}]"), json("[{\"b\": 1, \"c\": 2}]"))) .from(Arrays.asList(json("[{\"b\": 1}]"), json("[{\"b\": 1}]"), json("[{\"b\": 1, \"c\": 2}]")))
.getUncommon()).containsExactly("[].c"); .getUncommon()).containsExactly("[].c");
} }
@Test @Test
public void missingEntryBeneathNestedArrayIsIdentifiedAsUncommon() { void missingEntryBeneathNestedArrayIsIdentifiedAsUncommon() {
assertThat(JsonFieldPaths.from(Arrays.asList(json("{\"a\": [{\"b\": 1}]}"), json("{\"a\": [{\"b\": 1}]}"), assertThat(JsonFieldPaths.from(Arrays.asList(json("{\"a\": [{\"b\": 1}]}"), json("{\"a\": [{\"b\": 1}]}"),
json("{\"a\": [{\"b\": 1, \"c\": 2}]}"))) json("{\"a\": [{\"b\": 1, \"c\": 2}]}")))
.getUncommon()).containsExactly("a.[].c"); .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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with 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 java.util.Map;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.Test; import org.junit.jupiter.api.Test;
import org.springframework.restdocs.payload.JsonFieldProcessor.ExtractedField; import org.springframework.restdocs.payload.JsonFieldProcessor.ExtractedField;
@@ -37,19 +37,19 @@ import static org.assertj.core.api.Assertions.assertThat;
* *
* @author Andy Wilkinson * @author Andy Wilkinson
*/ */
public class JsonFieldProcessorTests { class JsonFieldProcessorTests {
private final JsonFieldProcessor fieldProcessor = new JsonFieldProcessor(); private final JsonFieldProcessor fieldProcessor = new JsonFieldProcessor();
@Test @Test
public void extractTopLevelMapEntry() { void extractTopLevelMapEntry() {
Map<String, Object> payload = new HashMap<>(); Map<String, Object> payload = new HashMap<>();
payload.put("a", "alpha"); payload.put("a", "alpha");
assertThat(this.fieldProcessor.extract("a", payload).getValue()).isEqualTo("alpha"); assertThat(this.fieldProcessor.extract("a", payload).getValue()).isEqualTo("alpha");
} }
@Test @Test
public void extractNestedMapEntry() { void extractNestedMapEntry() {
Map<String, Object> payload = new HashMap<>(); Map<String, Object> payload = new HashMap<>();
Map<String, Object> alpha = new HashMap<>(); Map<String, Object> alpha = new HashMap<>();
payload.put("a", alpha); payload.put("a", alpha);
@@ -58,7 +58,7 @@ public class JsonFieldProcessorTests {
} }
@Test @Test
public void extractTopLevelArray() { void extractTopLevelArray() {
List<Map<String, Object>> payload = new ArrayList<>(); List<Map<String, Object>> payload = new ArrayList<>();
Map<String, Object> bravo = new HashMap<>(); Map<String, Object> bravo = new HashMap<>();
bravo.put("b", "bravo"); bravo.put("b", "bravo");
@@ -68,7 +68,7 @@ public class JsonFieldProcessorTests {
} }
@Test @Test
public void extractArray() { void extractArray() {
Map<String, Object> payload = new HashMap<>(); Map<String, Object> payload = new HashMap<>();
Map<String, Object> bravo = new HashMap<>(); Map<String, Object> bravo = new HashMap<>();
bravo.put("b", "bravo"); bravo.put("b", "bravo");
@@ -78,7 +78,7 @@ public class JsonFieldProcessorTests {
} }
@Test @Test
public void extractArrayContents() { void extractArrayContents() {
Map<String, Object> payload = new HashMap<>(); Map<String, Object> payload = new HashMap<>();
Map<String, Object> bravo = new HashMap<>(); Map<String, Object> bravo = new HashMap<>();
bravo.put("b", "bravo"); bravo.put("b", "bravo");
@@ -88,7 +88,7 @@ public class JsonFieldProcessorTests {
} }
@Test @Test
public void extractFromItemsInArray() { void extractFromItemsInArray() {
Map<String, Object> payload = new HashMap<>(); Map<String, Object> payload = new HashMap<>();
Map<String, Object> entry = new HashMap<>(); Map<String, Object> entry = new HashMap<>();
entry.put("b", "bravo"); entry.put("b", "bravo");
@@ -98,7 +98,7 @@ public class JsonFieldProcessorTests {
} }
@Test @Test
public void extractOccasionallyAbsentFieldFromItemsInArray() { void extractOccasionallyAbsentFieldFromItemsInArray() {
Map<String, Object> payload = new HashMap<>(); Map<String, Object> payload = new HashMap<>();
Map<String, Object> entry = new HashMap<>(); Map<String, Object> entry = new HashMap<>();
entry.put("b", "bravo"); entry.put("b", "bravo");
@@ -109,7 +109,7 @@ public class JsonFieldProcessorTests {
} }
@Test @Test
public void extractOccasionallyNullFieldFromItemsInArray() { void extractOccasionallyNullFieldFromItemsInArray() {
Map<String, Object> payload = new HashMap<>(); Map<String, Object> payload = new HashMap<>();
Map<String, Object> nonNullField = new HashMap<>(); Map<String, Object> nonNullField = new HashMap<>();
nonNullField.put("b", "bravo"); nonNullField.put("b", "bravo");
@@ -121,7 +121,7 @@ public class JsonFieldProcessorTests {
} }
@Test @Test
public void extractNestedArray() { void extractNestedArray() {
Map<String, Object> payload = new HashMap<>(); Map<String, Object> payload = new HashMap<>();
Map<String, String> entry1 = createEntry("id:1"); Map<String, String> entry1 = createEntry("id:1");
Map<String, String> entry2 = createEntry("id:2"); Map<String, String> entry2 = createEntry("id:2");
@@ -133,7 +133,7 @@ public class JsonFieldProcessorTests {
} }
@Test @Test
public void extractFromItemsInNestedArray() { void extractFromItemsInNestedArray() {
Map<String, Object> payload = new HashMap<>(); Map<String, Object> payload = new HashMap<>();
Map<String, String> entry1 = createEntry("id:1"); Map<String, String> entry1 = createEntry("id:1");
Map<String, String> entry2 = createEntry("id:2"); Map<String, String> entry2 = createEntry("id:2");
@@ -144,7 +144,7 @@ public class JsonFieldProcessorTests {
} }
@Test @Test
public void extractArraysFromItemsInNestedArray() { void extractArraysFromItemsInNestedArray() {
Map<String, Object> payload = new HashMap<>(); Map<String, Object> payload = new HashMap<>();
Map<String, Object> entry1 = createEntry("ids", Arrays.asList(1, 2)); Map<String, Object> entry1 = createEntry("ids", Arrays.asList(1, 2));
Map<String, Object> entry2 = createEntry("ids", Arrays.asList(3)); Map<String, Object> entry2 = createEntry("ids", Arrays.asList(3));
@@ -156,27 +156,27 @@ public class JsonFieldProcessorTests {
} }
@Test @Test
public void nonExistentTopLevelField() { void nonExistentTopLevelField() {
assertThat(this.fieldProcessor.extract("a", Collections.emptyMap()).getValue()) assertThat(this.fieldProcessor.extract("a", Collections.emptyMap()).getValue())
.isEqualTo(ExtractedField.ABSENT); .isEqualTo(ExtractedField.ABSENT);
} }
@Test @Test
public void nonExistentNestedField() { void nonExistentNestedField() {
HashMap<String, Object> payload = new HashMap<>(); 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); assertThat(this.fieldProcessor.extract("a.b", payload).getValue()).isEqualTo(ExtractedField.ABSENT);
} }
@Test @Test
public void nonExistentNestedFieldWhenParentIsNotAMap() { void nonExistentNestedFieldWhenParentIsNotAMap() {
HashMap<String, Object> payload = new HashMap<>(); HashMap<String, Object> payload = new HashMap<>();
payload.put("a", 5); payload.put("a", 5);
assertThat(this.fieldProcessor.extract("a.b", payload).getValue()).isEqualTo(ExtractedField.ABSENT); assertThat(this.fieldProcessor.extract("a.b", payload).getValue()).isEqualTo(ExtractedField.ABSENT);
} }
@Test @Test
public void nonExistentFieldWhenParentIsAnArray() { void nonExistentFieldWhenParentIsAnArray() {
HashMap<String, Object> payload = new HashMap<>(); HashMap<String, Object> payload = new HashMap<>();
HashMap<String, Object> alpha = new HashMap<>(); HashMap<String, Object> alpha = new HashMap<>();
alpha.put("b", Arrays.asList(new HashMap<String, Object>())); alpha.put("b", Arrays.asList(new HashMap<String, Object>()));
@@ -185,20 +185,20 @@ public class JsonFieldProcessorTests {
} }
@Test @Test
public void nonExistentArrayField() { void nonExistentArrayField() {
HashMap<String, Object> payload = new HashMap<>(); HashMap<String, Object> payload = new HashMap<>();
assertThat(this.fieldProcessor.extract("a[]", payload).getValue()).isEqualTo(ExtractedField.ABSENT); assertThat(this.fieldProcessor.extract("a[]", payload).getValue()).isEqualTo(ExtractedField.ABSENT);
} }
@Test @Test
public void nonExistentArrayFieldAsTypeDoesNotMatch() { void nonExistentArrayFieldAsTypeDoesNotMatch() {
HashMap<String, Object> payload = new HashMap<>(); HashMap<String, Object> payload = new HashMap<>();
payload.put("a", 5); payload.put("a", 5);
assertThat(this.fieldProcessor.extract("a[]", payload).getValue()).isEqualTo(ExtractedField.ABSENT); assertThat(this.fieldProcessor.extract("a[]", payload).getValue()).isEqualTo(ExtractedField.ABSENT);
} }
@Test @Test
public void nonExistentFieldBeneathAnArray() { void nonExistentFieldBeneathAnArray() {
HashMap<String, Object> payload = new HashMap<>(); HashMap<String, Object> payload = new HashMap<>();
HashMap<String, Object> alpha = new HashMap<>(); HashMap<String, Object> alpha = new HashMap<>();
alpha.put("b", Arrays.asList(new HashMap<String, Object>())); alpha.put("b", Arrays.asList(new HashMap<String, Object>()));
@@ -208,7 +208,7 @@ public class JsonFieldProcessorTests {
} }
@Test @Test
public void removeTopLevelMapEntry() { void removeTopLevelMapEntry() {
Map<String, Object> payload = new HashMap<>(); Map<String, Object> payload = new HashMap<>();
payload.put("a", "alpha"); payload.put("a", "alpha");
this.fieldProcessor.remove("a", payload); this.fieldProcessor.remove("a", payload);
@@ -216,7 +216,7 @@ public class JsonFieldProcessorTests {
} }
@Test @Test
public void mapWithEntriesIsNotRemovedWhenNotAlsoRemovingDescendants() { void mapWithEntriesIsNotRemovedWhenNotAlsoRemovingDescendants() {
Map<String, Object> payload = new HashMap<>(); Map<String, Object> payload = new HashMap<>();
Map<String, Object> alpha = new HashMap<>(); Map<String, Object> alpha = new HashMap<>();
payload.put("a", alpha); payload.put("a", alpha);
@@ -226,7 +226,7 @@ public class JsonFieldProcessorTests {
} }
@Test @Test
public void removeSubsectionRemovesMapWithEntries() { void removeSubsectionRemovesMapWithEntries() {
Map<String, Object> payload = new HashMap<>(); Map<String, Object> payload = new HashMap<>();
Map<String, Object> alpha = new HashMap<>(); Map<String, Object> alpha = new HashMap<>();
payload.put("a", alpha); payload.put("a", alpha);
@@ -236,7 +236,7 @@ public class JsonFieldProcessorTests {
} }
@Test @Test
public void removeNestedMapEntry() { void removeNestedMapEntry() {
Map<String, Object> payload = new HashMap<>(); Map<String, Object> payload = new HashMap<>();
Map<String, Object> alpha = new HashMap<>(); Map<String, Object> alpha = new HashMap<>();
payload.put("a", alpha); payload.put("a", alpha);
@@ -247,7 +247,7 @@ public class JsonFieldProcessorTests {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
@Test @Test
public void removeItemsInArray() throws IOException { void removeItemsInArray() throws IOException {
Map<String, Object> payload = new ObjectMapper().readValue("{\"a\": [{\"b\":\"bravo\"},{\"b\":\"bravo\"}]}", Map<String, Object> payload = new ObjectMapper().readValue("{\"a\": [{\"b\":\"bravo\"},{\"b\":\"bravo\"}]}",
Map.class); Map.class);
this.fieldProcessor.remove("a[].b", payload); this.fieldProcessor.remove("a[].b", payload);
@@ -256,7 +256,7 @@ public class JsonFieldProcessorTests {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
@Test @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<String, Object> payload = new ObjectMapper().readValue("{\"a\": [[{\"id\":1},{\"id\":2}], [{\"id\":3}]]}",
Map.class); Map.class);
this.fieldProcessor.remove("a[][].id", payload); this.fieldProcessor.remove("a[][].id", payload);
@@ -265,7 +265,7 @@ public class JsonFieldProcessorTests {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
@Test @Test
public void removeDoesNotRemoveArrayWithMapEntries() throws IOException { void removeDoesNotRemoveArrayWithMapEntries() throws IOException {
Map<String, Object> payload = new ObjectMapper().readValue("{\"a\": [{\"b\":\"bravo\"},{\"b\":\"bravo\"}]}", Map<String, Object> payload = new ObjectMapper().readValue("{\"a\": [{\"b\":\"bravo\"},{\"b\":\"bravo\"}]}",
Map.class); Map.class);
this.fieldProcessor.remove("a[]", payload); this.fieldProcessor.remove("a[]", payload);
@@ -274,7 +274,7 @@ public class JsonFieldProcessorTests {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
@Test @Test
public void removeDoesNotRemoveArrayWithListEntries() throws IOException { void removeDoesNotRemoveArrayWithListEntries() throws IOException {
Map<String, Object> payload = new ObjectMapper().readValue("{\"a\": [[2],[3]]}", Map.class); Map<String, Object> payload = new ObjectMapper().readValue("{\"a\": [[2],[3]]}", Map.class);
this.fieldProcessor.remove("a[]", payload); this.fieldProcessor.remove("a[]", payload);
assertThat(payload.size()).isEqualTo(1); assertThat(payload.size()).isEqualTo(1);
@@ -282,7 +282,7 @@ public class JsonFieldProcessorTests {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
@Test @Test
public void removeRemovesArrayWithOnlyScalarEntries() throws IOException { void removeRemovesArrayWithOnlyScalarEntries() throws IOException {
Map<String, Object> payload = new ObjectMapper().readValue("{\"a\": [\"bravo\", \"charlie\"]}", Map.class); Map<String, Object> payload = new ObjectMapper().readValue("{\"a\": [\"bravo\", \"charlie\"]}", Map.class);
this.fieldProcessor.remove("a", payload); this.fieldProcessor.remove("a", payload);
assertThat(payload.size()).isEqualTo(0); assertThat(payload.size()).isEqualTo(0);
@@ -290,7 +290,7 @@ public class JsonFieldProcessorTests {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
@Test @Test
public void removeSubsectionRemovesArrayWithMapEntries() throws IOException { void removeSubsectionRemovesArrayWithMapEntries() throws IOException {
Map<String, Object> payload = new ObjectMapper().readValue("{\"a\": [{\"b\":\"bravo\"},{\"b\":\"bravo\"}]}", Map<String, Object> payload = new ObjectMapper().readValue("{\"a\": [{\"b\":\"bravo\"},{\"b\":\"bravo\"}]}",
Map.class); Map.class);
this.fieldProcessor.removeSubsection("a[]", payload); this.fieldProcessor.removeSubsection("a[]", payload);
@@ -299,14 +299,14 @@ public class JsonFieldProcessorTests {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
@Test @Test
public void removeSubsectionRemovesArrayWithListEntries() throws IOException { void removeSubsectionRemovesArrayWithListEntries() throws IOException {
Map<String, Object> payload = new ObjectMapper().readValue("{\"a\": [[2],[3]]}", Map.class); Map<String, Object> payload = new ObjectMapper().readValue("{\"a\": [[2],[3]]}", Map.class);
this.fieldProcessor.removeSubsection("a[]", payload); this.fieldProcessor.removeSubsection("a[]", payload);
assertThat(payload.size()).isEqualTo(0); assertThat(payload.size()).isEqualTo(0);
} }
@Test @Test
public void extractNestedEntryWithDotInKeys() { void extractNestedEntryWithDotInKeys() {
Map<String, Object> payload = new HashMap<>(); Map<String, Object> payload = new HashMap<>();
Map<String, Object> alpha = new HashMap<>(); Map<String, Object> alpha = new HashMap<>();
payload.put("a.key", alpha); payload.put("a.key", alpha);
@@ -316,7 +316,7 @@ public class JsonFieldProcessorTests {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
@Test @Test
public void extractNestedEntriesUsingTopLevelWildcard() { void extractNestedEntriesUsingTopLevelWildcard() {
Map<String, Object> payload = new LinkedHashMap<>(); Map<String, Object> payload = new LinkedHashMap<>();
Map<String, Object> alpha = new LinkedHashMap<>(); Map<String, Object> alpha = new LinkedHashMap<>();
payload.put("a", alpha); payload.put("a", alpha);
@@ -330,7 +330,7 @@ public class JsonFieldProcessorTests {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
@Test @Test
public void extractNestedEntriesUsingMidLevelWildcard() { void extractNestedEntriesUsingMidLevelWildcard() {
Map<String, Object> payload = new LinkedHashMap<>(); Map<String, Object> payload = new LinkedHashMap<>();
Map<String, Object> alpha = new LinkedHashMap<>(); Map<String, Object> alpha = new LinkedHashMap<>();
payload.put("a", alpha); payload.put("a", alpha);
@@ -344,7 +344,7 @@ public class JsonFieldProcessorTests {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
@Test @Test
public void extractUsingLeafWildcardMatchingSingleItem() { void extractUsingLeafWildcardMatchingSingleItem() {
Map<String, Object> payload = new HashMap<>(); Map<String, Object> payload = new HashMap<>();
Map<String, Object> alpha = new HashMap<>(); Map<String, Object> alpha = new HashMap<>();
payload.put("a", alpha); payload.put("a", alpha);
@@ -357,7 +357,7 @@ public class JsonFieldProcessorTests {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
@Test @Test
public void extractUsingLeafWildcardMatchingMultipleItems() { void extractUsingLeafWildcardMatchingMultipleItems() {
Map<String, Object> payload = new HashMap<>(); Map<String, Object> payload = new HashMap<>();
Map<String, Object> alpha = new HashMap<>(); Map<String, Object> alpha = new HashMap<>();
payload.put("a", alpha); payload.put("a", alpha);
@@ -368,7 +368,7 @@ public class JsonFieldProcessorTests {
} }
@Test @Test
public void removeUsingLeafWildcard() { void removeUsingLeafWildcard() {
Map<String, Object> payload = new HashMap<>(); Map<String, Object> payload = new HashMap<>();
Map<String, Object> alpha = new HashMap<>(); Map<String, Object> alpha = new HashMap<>();
payload.put("a", alpha); payload.put("a", alpha);
@@ -379,7 +379,7 @@ public class JsonFieldProcessorTests {
} }
@Test @Test
public void removeUsingTopLevelWildcard() { void removeUsingTopLevelWildcard() {
Map<String, Object> payload = new HashMap<>(); Map<String, Object> payload = new HashMap<>();
Map<String, Object> alpha = new HashMap<>(); Map<String, Object> alpha = new HashMap<>();
payload.put("a", alpha); payload.put("a", alpha);
@@ -390,7 +390,7 @@ public class JsonFieldProcessorTests {
} }
@Test @Test
public void removeUsingMidLevelWildcard() { void removeUsingMidLevelWildcard() {
Map<String, Object> payload = new LinkedHashMap<>(); Map<String, Object> payload = new LinkedHashMap<>();
Map<String, Object> alpha = new LinkedHashMap<>(); Map<String, Object> alpha = new LinkedHashMap<>();
payload.put("a", alpha); payload.put("a", alpha);
@@ -407,28 +407,28 @@ public class JsonFieldProcessorTests {
} }
@Test @Test
public void hasFieldIsTrueForNonNullFieldInMap() { void hasFieldIsTrueForNonNullFieldInMap() {
Map<String, Object> payload = new HashMap<>(); Map<String, Object> payload = new HashMap<>();
payload.put("a", "alpha"); payload.put("a", "alpha");
assertThat(this.fieldProcessor.hasField("a", payload)).isTrue(); assertThat(this.fieldProcessor.hasField("a", payload)).isTrue();
} }
@Test @Test
public void hasFieldIsTrueForNullFieldInMap() { void hasFieldIsTrueForNullFieldInMap() {
Map<String, Object> payload = new HashMap<>(); Map<String, Object> payload = new HashMap<>();
payload.put("a", null); payload.put("a", null);
assertThat(this.fieldProcessor.hasField("a", payload)).isTrue(); assertThat(this.fieldProcessor.hasField("a", payload)).isTrue();
} }
@Test @Test
public void hasFieldIsFalseForAbsentFieldInMap() { void hasFieldIsFalseForAbsentFieldInMap() {
Map<String, Object> payload = new HashMap<>(); Map<String, Object> payload = new HashMap<>();
payload.put("a", null); payload.put("a", null);
assertThat(this.fieldProcessor.hasField("b", payload)).isFalse(); assertThat(this.fieldProcessor.hasField("b", payload)).isFalse();
} }
@Test @Test
public void hasFieldIsTrueForNeverNullFieldBeneathArray() { void hasFieldIsTrueForNeverNullFieldBeneathArray() {
Map<String, Object> payload = new HashMap<>(); Map<String, Object> payload = new HashMap<>();
Map<String, Object> nested = new HashMap<>(); Map<String, Object> nested = new HashMap<>();
nested.put("b", "bravo"); nested.put("b", "bravo");
@@ -437,7 +437,7 @@ public class JsonFieldProcessorTests {
} }
@Test @Test
public void hasFieldIsTrueForAlwaysNullFieldBeneathArray() { void hasFieldIsTrueForAlwaysNullFieldBeneathArray() {
Map<String, Object> payload = new HashMap<>(); Map<String, Object> payload = new HashMap<>();
Map<String, Object> nested = new HashMap<>(); Map<String, Object> nested = new HashMap<>();
nested.put("b", null); nested.put("b", null);
@@ -446,7 +446,7 @@ public class JsonFieldProcessorTests {
} }
@Test @Test
public void hasFieldIsFalseForAlwaysAbsentFieldBeneathArray() { void hasFieldIsFalseForAlwaysAbsentFieldBeneathArray() {
Map<String, Object> payload = new HashMap<>(); Map<String, Object> payload = new HashMap<>();
Map<String, Object> nested = new HashMap<>(); Map<String, Object> nested = new HashMap<>();
nested.put("b", "bravo"); nested.put("b", "bravo");
@@ -455,7 +455,7 @@ public class JsonFieldProcessorTests {
} }
@Test @Test
public void hasFieldIsFalseForOccasionallyAbsentFieldBeneathArray() { void hasFieldIsFalseForOccasionallyAbsentFieldBeneathArray() {
Map<String, Object> payload = new HashMap<>(); Map<String, Object> payload = new HashMap<>();
Map<String, Object> nested = new HashMap<>(); Map<String, Object> nested = new HashMap<>();
nested.put("b", "bravo"); nested.put("b", "bravo");
@@ -464,7 +464,7 @@ public class JsonFieldProcessorTests {
} }
@Test @Test
public void hasFieldIsFalseForOccasionallyNullFieldBeneathArray() { void hasFieldIsFalseForOccasionallyNullFieldBeneathArray() {
Map<String, Object> payload = new HashMap<>(); Map<String, Object> payload = new HashMap<>();
Map<String, Object> fieldPresent = new HashMap<>(); Map<String, Object> fieldPresent = new HashMap<>();
fieldPresent.put("b", "bravo"); 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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with 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 java.io.IOException;
import com.fasterxml.jackson.databind.ObjectMapper; 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.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
@@ -29,148 +29,148 @@ import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
* *
* @author Andy Wilkinson * @author Andy Wilkinson
*/ */
public class JsonFieldTypesDiscovererTests { class JsonFieldTypesDiscovererTests {
private final JsonFieldTypesDiscoverer fieldTypeDiscoverer = new JsonFieldTypesDiscoverer(); private final JsonFieldTypesDiscoverer fieldTypeDiscoverer = new JsonFieldTypesDiscoverer();
@Test @Test
public void arrayField() throws IOException { void arrayField() throws IOException {
assertThat(discoverFieldTypes("[]")).containsExactly(JsonFieldType.ARRAY); assertThat(discoverFieldTypes("[]")).containsExactly(JsonFieldType.ARRAY);
} }
@Test @Test
public void topLevelArray() throws IOException { void topLevelArray() throws IOException {
assertThat(discoverFieldTypes("[]", "[{\"a\":\"alpha\"}]")).containsExactly(JsonFieldType.ARRAY); assertThat(discoverFieldTypes("[]", "[{\"a\":\"alpha\"}]")).containsExactly(JsonFieldType.ARRAY);
} }
@Test @Test
public void nestedArray() throws IOException { void nestedArray() throws IOException {
assertThat(discoverFieldTypes("a[]", "{\"a\": [{\"b\":\"bravo\"}]}")).containsExactly(JsonFieldType.ARRAY); assertThat(discoverFieldTypes("a[]", "{\"a\": [{\"b\":\"bravo\"}]}")).containsExactly(JsonFieldType.ARRAY);
} }
@Test @Test
public void arrayNestedBeneathAnArray() throws IOException { void arrayNestedBeneathAnArray() throws IOException {
assertThat(discoverFieldTypes("a[].b[]", "{\"a\": [{\"b\": [ 1, 2 ]}]}")).containsExactly(JsonFieldType.ARRAY); assertThat(discoverFieldTypes("a[].b[]", "{\"a\": [{\"b\": [ 1, 2 ]}]}")).containsExactly(JsonFieldType.ARRAY);
} }
@Test @Test
public void specificFieldOfObjectInArrayNestedBeneathAnArray() throws IOException { void specificFieldOfObjectInArrayNestedBeneathAnArray() throws IOException {
assertThat(discoverFieldTypes("a[].b[].c", "{\"a\": [{\"b\": [ {\"c\": 5}, {\"c\": 5}]}]}")) assertThat(discoverFieldTypes("a[].b[].c", "{\"a\": [{\"b\": [ {\"c\": 5}, {\"c\": 5}]}]}"))
.containsExactly(JsonFieldType.NUMBER); .containsExactly(JsonFieldType.NUMBER);
} }
@Test @Test
public void booleanField() throws IOException { void booleanField() throws IOException {
assertThat(discoverFieldTypes("true")).containsExactly(JsonFieldType.BOOLEAN); assertThat(discoverFieldTypes("true")).containsExactly(JsonFieldType.BOOLEAN);
} }
@Test @Test
public void objectField() throws IOException { void objectField() throws IOException {
assertThat(discoverFieldTypes("{}")).containsExactly(JsonFieldType.OBJECT); assertThat(discoverFieldTypes("{}")).containsExactly(JsonFieldType.OBJECT);
} }
@Test @Test
public void nullField() throws IOException { void nullField() throws IOException {
assertThat(discoverFieldTypes("null")).containsExactly(JsonFieldType.NULL); assertThat(discoverFieldTypes("null")).containsExactly(JsonFieldType.NULL);
} }
@Test @Test
public void numberField() throws IOException { void numberField() throws IOException {
assertThat(discoverFieldTypes("1.2345")).containsExactly(JsonFieldType.NUMBER); assertThat(discoverFieldTypes("1.2345")).containsExactly(JsonFieldType.NUMBER);
} }
@Test @Test
public void stringField() throws IOException { void stringField() throws IOException {
assertThat(discoverFieldTypes("\"Foo\"")).containsExactly(JsonFieldType.STRING); assertThat(discoverFieldTypes("\"Foo\"")).containsExactly(JsonFieldType.STRING);
} }
@Test @Test
public void nestedField() throws IOException { void nestedField() throws IOException {
assertThat(discoverFieldTypes("a.b.c", "{\"a\":{\"b\":{\"c\":{}}}}")).containsExactly(JsonFieldType.OBJECT); assertThat(discoverFieldTypes("a.b.c", "{\"a\":{\"b\":{\"c\":{}}}}")).containsExactly(JsonFieldType.OBJECT);
} }
@Test @Test
public void multipleFieldsWithSameType() throws IOException { void multipleFieldsWithSameType() throws IOException {
assertThat(discoverFieldTypes("a[].id", "{\"a\":[{\"id\":1},{\"id\":2}]}")) assertThat(discoverFieldTypes("a[].id", "{\"a\":[{\"id\":1},{\"id\":2}]}"))
.containsExactly(JsonFieldType.NUMBER); .containsExactly(JsonFieldType.NUMBER);
} }
@Test @Test
public void multipleFieldsWithDifferentTypes() throws IOException { void multipleFieldsWithDifferentTypes() throws IOException {
assertThat(discoverFieldTypes("a[].id", "{\"a\":[{\"id\":1},{\"id\":true}]}")) assertThat(discoverFieldTypes("a[].id", "{\"a\":[{\"id\":1},{\"id\":true}]}"))
.containsExactlyInAnyOrder(JsonFieldType.NUMBER, JsonFieldType.BOOLEAN); .containsExactlyInAnyOrder(JsonFieldType.NUMBER, JsonFieldType.BOOLEAN);
} }
@Test @Test
public void multipleFieldsWithDifferentTypesAndSometimesAbsent() throws IOException { void multipleFieldsWithDifferentTypesAndSometimesAbsent() throws IOException {
assertThat(discoverFieldTypes("a[].id", "{\"a\":[{\"id\":1},{\"id\":true}, {}]}")) assertThat(discoverFieldTypes("a[].id", "{\"a\":[{\"id\":1},{\"id\":true}, {}]}"))
.containsExactlyInAnyOrder(JsonFieldType.NUMBER, JsonFieldType.BOOLEAN, JsonFieldType.NULL); .containsExactlyInAnyOrder(JsonFieldType.NUMBER, JsonFieldType.BOOLEAN, JsonFieldType.NULL);
} }
@Test @Test
public void multipleFieldsWhenSometimesAbsent() throws IOException { void multipleFieldsWhenSometimesAbsent() throws IOException {
assertThat(discoverFieldTypes("a[].id", "{\"a\":[{\"id\":1},{\"id\":2}, {}]}")) assertThat(discoverFieldTypes("a[].id", "{\"a\":[{\"id\":1},{\"id\":2}, {}]}"))
.containsExactlyInAnyOrder(JsonFieldType.NUMBER, JsonFieldType.NULL); .containsExactlyInAnyOrder(JsonFieldType.NUMBER, JsonFieldType.NULL);
} }
@Test @Test
public void multipleFieldsWhenSometimesNull() throws IOException { void multipleFieldsWhenSometimesNull() throws IOException {
assertThat(discoverFieldTypes("a[].id", "{\"a\":[{\"id\":1},{\"id\":2}, {\"id\":null}]}")) assertThat(discoverFieldTypes("a[].id", "{\"a\":[{\"id\":1},{\"id\":2}, {\"id\":null}]}"))
.containsExactlyInAnyOrder(JsonFieldType.NUMBER, JsonFieldType.NULL); .containsExactlyInAnyOrder(JsonFieldType.NUMBER, JsonFieldType.NULL);
} }
@Test @Test
public void multipleFieldsWithDifferentTypesAndSometimesNull() throws IOException { void multipleFieldsWithDifferentTypesAndSometimesNull() throws IOException {
assertThat(discoverFieldTypes("a[].id", "{\"a\":[{\"id\":1},{\"id\":true}, {\"id\":null}]}")) assertThat(discoverFieldTypes("a[].id", "{\"a\":[{\"id\":1},{\"id\":true}, {\"id\":null}]}"))
.containsExactlyInAnyOrder(JsonFieldType.NUMBER, JsonFieldType.BOOLEAN, JsonFieldType.NULL); .containsExactlyInAnyOrder(JsonFieldType.NUMBER, JsonFieldType.BOOLEAN, JsonFieldType.NULL);
} }
@Test @Test
public void multipleFieldsWhenEitherNullOrAbsent() throws IOException { void multipleFieldsWhenEitherNullOrAbsent() throws IOException {
assertThat(discoverFieldTypes("a[].id", "{\"a\":[{},{\"id\":null}]}")) assertThat(discoverFieldTypes("a[].id", "{\"a\":[{},{\"id\":null}]}"))
.containsExactlyInAnyOrder(JsonFieldType.NULL); .containsExactlyInAnyOrder(JsonFieldType.NULL);
} }
@Test @Test
public void multipleFieldsThatAreAllNull() throws IOException { void multipleFieldsThatAreAllNull() throws IOException {
assertThat(discoverFieldTypes("a[].id", "{\"a\":[{\"id\":null},{\"id\":null}]}")) assertThat(discoverFieldTypes("a[].id", "{\"a\":[{\"id\":null},{\"id\":null}]}"))
.containsExactlyInAnyOrder(JsonFieldType.NULL); .containsExactlyInAnyOrder(JsonFieldType.NULL);
} }
@Test @Test
public void nonExistentSingleFieldProducesFieldDoesNotExistException() { void nonExistentSingleFieldProducesFieldDoesNotExistException() {
assertThatExceptionOfType(FieldDoesNotExistException.class) assertThatExceptionOfType(FieldDoesNotExistException.class)
.isThrownBy(() -> discoverFieldTypes("a.b", "{\"a\":{}}")) .isThrownBy(() -> discoverFieldTypes("a.b", "{\"a\":{}}"))
.withMessage("The payload does not contain a field with the path 'a.b'"); .withMessage("The payload does not contain a field with the path 'a.b'");
} }
@Test @Test
public void nonExistentMultipleFieldsProducesFieldDoesNotExistException() { void nonExistentMultipleFieldsProducesFieldDoesNotExistException() {
assertThatExceptionOfType(FieldDoesNotExistException.class) assertThatExceptionOfType(FieldDoesNotExistException.class)
.isThrownBy(() -> discoverFieldTypes("a[].b", "{\"a\":[{\"c\":1},{\"c\":2}]}")) .isThrownBy(() -> discoverFieldTypes("a[].b", "{\"a\":[{\"c\":1},{\"c\":2}]}"))
.withMessage("The payload does not contain a field with the path 'a[].b'"); .withMessage("The payload does not contain a field with the path 'a[].b'");
} }
@Test @Test
public void leafWildcardWithCommonType() throws IOException { void leafWildcardWithCommonType() throws IOException {
assertThat(discoverFieldTypes("a.*", "{\"a\": {\"b\": 5, \"c\": 6}}")) assertThat(discoverFieldTypes("a.*", "{\"a\": {\"b\": 5, \"c\": 6}}"))
.containsExactlyInAnyOrder(JsonFieldType.NUMBER); .containsExactlyInAnyOrder(JsonFieldType.NUMBER);
} }
@Test @Test
public void leafWildcardWithVaryingType() throws IOException { void leafWildcardWithVaryingType() throws IOException {
assertThat(discoverFieldTypes("a.*", "{\"a\": {\"b\": 5, \"c\": \"six\"}}")) assertThat(discoverFieldTypes("a.*", "{\"a\": {\"b\": 5, \"c\": \"six\"}}"))
.containsExactlyInAnyOrder(JsonFieldType.NUMBER, JsonFieldType.STRING); .containsExactlyInAnyOrder(JsonFieldType.NUMBER, JsonFieldType.STRING);
} }
@Test @Test
public void intermediateWildcardWithCommonType() throws IOException { void intermediateWildcardWithCommonType() throws IOException {
assertThat(discoverFieldTypes("a.*.d", "{\"a\": {\"b\": {\"d\": 4}, \"c\": {\"d\": 5}}}}")) assertThat(discoverFieldTypes("a.*.d", "{\"a\": {\"b\": {\"d\": 4}, \"c\": {\"d\": 5}}}}"))
.containsExactlyInAnyOrder(JsonFieldType.NUMBER); .containsExactlyInAnyOrder(JsonFieldType.NUMBER);
} }
@Test @Test
public void intermediateWildcardWithVaryingType() throws IOException { void intermediateWildcardWithVaryingType() throws IOException {
assertThat(discoverFieldTypes("a.*.d", "{\"a\": {\"b\": {\"d\": 4}, \"c\": {\"d\": \"four\"}}}}")) assertThat(discoverFieldTypes("a.*.d", "{\"a\": {\"b\": {\"d\": 4}, \"c\": {\"d\": \"four\"}}}}"))
.containsExactlyInAnyOrder(JsonFieldType.NUMBER, JsonFieldType.STRING); .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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with 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 java.util.EnumSet;
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.assertThat;
@@ -27,32 +27,32 @@ import static org.assertj.core.api.Assertions.assertThat;
* *
* @author Andy Wilkinson * @author Andy Wilkinson
*/ */
public class JsonFieldTypesTests { class JsonFieldTypesTests {
@Test @Test
public void singleTypeCoalescesToThatType() { void singleTypeCoalescesToThatType() {
assertThat(new JsonFieldTypes(JsonFieldType.NUMBER).coalesce(false)).isEqualTo(JsonFieldType.NUMBER); assertThat(new JsonFieldTypes(JsonFieldType.NUMBER).coalesce(false)).isEqualTo(JsonFieldType.NUMBER);
} }
@Test @Test
public void singleTypeCoalescesToThatTypeWhenOptional() { void singleTypeCoalescesToThatTypeWhenOptional() {
assertThat(new JsonFieldTypes(JsonFieldType.NUMBER).coalesce(true)).isEqualTo(JsonFieldType.NUMBER); assertThat(new JsonFieldTypes(JsonFieldType.NUMBER).coalesce(true)).isEqualTo(JsonFieldType.NUMBER);
} }
@Test @Test
public void multipleTypesCoalescesToVaries() { void multipleTypesCoalescesToVaries() {
assertThat(new JsonFieldTypes(EnumSet.of(JsonFieldType.ARRAY, JsonFieldType.NUMBER)).coalesce(false)) assertThat(new JsonFieldTypes(EnumSet.of(JsonFieldType.ARRAY, JsonFieldType.NUMBER)).coalesce(false))
.isEqualTo(JsonFieldType.VARIES); .isEqualTo(JsonFieldType.VARIES);
} }
@Test @Test
public void nullAndNonNullTypesCoalescesToVaries() { void nullAndNonNullTypesCoalescesToVaries() {
assertThat(new JsonFieldTypes(EnumSet.of(JsonFieldType.ARRAY, JsonFieldType.NULL)).coalesce(false)) assertThat(new JsonFieldTypes(EnumSet.of(JsonFieldType.ARRAY, JsonFieldType.NULL)).coalesce(false))
.isEqualTo(JsonFieldType.VARIES); .isEqualTo(JsonFieldType.VARIES);
} }
@Test @Test
public void nullAndNonNullTypesCoalescesToNonNullTypeWhenOptional() { void nullAndNonNullTypesCoalescesToNonNullTypeWhenOptional() {
assertThat(new JsonFieldTypes(EnumSet.of(JsonFieldType.ARRAY, JsonFieldType.NULL)).coalesce(true)) assertThat(new JsonFieldTypes(EnumSet.of(JsonFieldType.ARRAY, JsonFieldType.NULL)).coalesce(true))
.isEqualTo(JsonFieldType.ARRAY); .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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with 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.Arrays;
import java.util.List; 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.assertThat;
import static org.springframework.restdocs.payload.PayloadDocumentation.applyPathPrefix; import static org.springframework.restdocs.payload.PayloadDocumentation.applyPathPrefix;
@@ -31,10 +31,10 @@ import static org.springframework.restdocs.snippet.Attributes.key;
* *
* @author Andy Wilkinson * @author Andy Wilkinson
*/ */
public class PayloadDocumentationTests { class PayloadDocumentationTests {
@Test @Test
public void applyPathPrefixAppliesPrefixToDescriptorPaths() { void applyPathPrefixAppliesPrefixToDescriptorPaths() {
List<FieldDescriptor> descriptors = applyPathPrefix("alpha.", List<FieldDescriptor> descriptors = applyPathPrefix("alpha.",
Arrays.asList(fieldWithPath("bravo"), fieldWithPath("charlie"))); Arrays.asList(fieldWithPath("bravo"), fieldWithPath("charlie")));
assertThat(descriptors.size()).isEqualTo(2); assertThat(descriptors.size()).isEqualTo(2);
@@ -42,21 +42,21 @@ public class PayloadDocumentationTests {
} }
@Test @Test
public void applyPathPrefixCopiesIgnored() { void applyPathPrefixCopiesIgnored() {
List<FieldDescriptor> descriptors = applyPathPrefix("alpha.", Arrays.asList(fieldWithPath("bravo").ignored())); List<FieldDescriptor> descriptors = applyPathPrefix("alpha.", Arrays.asList(fieldWithPath("bravo").ignored()));
assertThat(descriptors.size()).isEqualTo(1); assertThat(descriptors.size()).isEqualTo(1);
assertThat(descriptors.get(0).isIgnored()).isTrue(); assertThat(descriptors.get(0).isIgnored()).isTrue();
} }
@Test @Test
public void applyPathPrefixCopiesOptional() { void applyPathPrefixCopiesOptional() {
List<FieldDescriptor> descriptors = applyPathPrefix("alpha.", Arrays.asList(fieldWithPath("bravo").optional())); List<FieldDescriptor> descriptors = applyPathPrefix("alpha.", Arrays.asList(fieldWithPath("bravo").optional()));
assertThat(descriptors.size()).isEqualTo(1); assertThat(descriptors.size()).isEqualTo(1);
assertThat(descriptors.get(0).isOptional()).isTrue(); assertThat(descriptors.get(0).isOptional()).isTrue();
} }
@Test @Test
public void applyPathPrefixCopiesDescription() { void applyPathPrefixCopiesDescription() {
List<FieldDescriptor> descriptors = applyPathPrefix("alpha.", List<FieldDescriptor> descriptors = applyPathPrefix("alpha.",
Arrays.asList(fieldWithPath("bravo").description("Some field"))); Arrays.asList(fieldWithPath("bravo").description("Some field")));
assertThat(descriptors.size()).isEqualTo(1); assertThat(descriptors.size()).isEqualTo(1);
@@ -64,7 +64,7 @@ public class PayloadDocumentationTests {
} }
@Test @Test
public void applyPathPrefixCopiesType() { void applyPathPrefixCopiesType() {
List<FieldDescriptor> descriptors = applyPathPrefix("alpha.", List<FieldDescriptor> descriptors = applyPathPrefix("alpha.",
Arrays.asList(fieldWithPath("bravo").type(JsonFieldType.OBJECT))); Arrays.asList(fieldWithPath("bravo").type(JsonFieldType.OBJECT)));
assertThat(descriptors.size()).isEqualTo(1); assertThat(descriptors.size()).isEqualTo(1);
@@ -72,7 +72,7 @@ public class PayloadDocumentationTests {
} }
@Test @Test
public void applyPathPrefixCopiesAttributes() { void applyPathPrefixCopiesAttributes() {
List<FieldDescriptor> descriptors = applyPathPrefix("alpha.", List<FieldDescriptor> descriptors = applyPathPrefix("alpha.",
Arrays.asList(fieldWithPath("bravo").attributes(key("a").value("alpha"), key("b").value("bravo")))); Arrays.asList(fieldWithPath("bravo").attributes(key("a").value("alpha"), key("b").value("bravo"))));
assertThat(descriptors.size()).isEqualTo(1); 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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with 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 java.io.IOException;
import org.junit.Test;
import org.springframework.http.HttpHeaders; import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.restdocs.AbstractSnippetTests; import org.springframework.restdocs.testfixtures.jupiter.AssertableSnippets;
import org.springframework.restdocs.templates.TemplateEngine; import org.springframework.restdocs.testfixtures.jupiter.OperationBuilder;
import org.springframework.restdocs.templates.TemplateFormat; import org.springframework.restdocs.testfixtures.jupiter.RenderedSnippetTest;
import org.springframework.restdocs.templates.TemplateResourceResolver; import org.springframework.restdocs.testfixtures.jupiter.SnippetTemplate;
import org.springframework.restdocs.templates.mustache.MustacheTemplateEngine;
import static org.assertj.core.api.Assertions.assertThat; 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.beneathPath;
import static org.springframework.restdocs.payload.PayloadDocumentation.requestPartBody; import static org.springframework.restdocs.payload.PayloadDocumentation.requestPartBody;
import static org.springframework.restdocs.snippet.Attributes.attributes; import static org.springframework.restdocs.snippet.Attributes.attributes;
@@ -41,89 +36,84 @@ import static org.springframework.restdocs.snippet.Attributes.key;
* *
* @author Andy Wilkinson * @author Andy Wilkinson
*/ */
public class RequestBodyPartSnippetTests extends AbstractSnippetTests { class RequestBodyPartSnippetTests {
public RequestBodyPartSnippetTests(String name, TemplateFormat templateFormat) { @RenderedSnippetTest
super(name, templateFormat); void requestPartWithBody(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
}
@Test
public void requestPartWithBody() throws IOException {
requestPartBody("one") requestPartBody("one")
.document(this.operationBuilder.request("http://localhost").part("one", "some content".getBytes()).build()); .document(operationBuilder.request("http://localhost").part("one", "some content".getBytes()).build());
assertThat(this.generatedSnippets.snippet("request-part-one-body")) assertThat(snippets.requestPartBody("one"))
.is(codeBlock(null, "nowrap").withContent("some content")); .isCodeBlock((codeBlock) -> codeBlock.withOptions("nowrap").content("some content"));
} }
@Test @RenderedSnippetTest
public void requestPartWithNoBody() throws IOException { void requestPartWithNoBody(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
requestPartBody("one") requestPartBody("one").document(operationBuilder.request("http://localhost").part("one", new byte[0]).build());
.document(this.operationBuilder.request("http://localhost").part("one", new byte[0]).build()); assertThat(snippets.requestPartBody("one"))
assertThat(this.generatedSnippets.snippet("request-part-one-body")) .isCodeBlock((codeBlock) -> codeBlock.withOptions("nowrap").content(""));
.is(codeBlock(null, "nowrap").withContent(""));
} }
@Test @RenderedSnippetTest
public void requestPartWithJsonMediaType() throws IOException { void requestPartWithJsonMediaType(OperationBuilder operationBuilder, AssertableSnippets snippets)
requestPartBody("one").document(this.operationBuilder.request("http://localhost") throws IOException {
requestPartBody("one").document(operationBuilder.request("http://localhost")
.part("one", "".getBytes()) .part("one", "".getBytes())
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
.build()); .build());
assertThat(this.generatedSnippets.snippet("request-part-one-body")) assertThat(snippets.requestPartBody("one"))
.is(codeBlock("json", "nowrap").withContent("")); .isCodeBlock((codeBlock) -> codeBlock.withLanguageAndOptions("json", "nowrap").content(""));
} }
@Test @RenderedSnippetTest
public void requestPartWithJsonSubtypeMediaType() throws IOException { void requestPartWithJsonSubtypeMediaType(OperationBuilder operationBuilder, AssertableSnippets snippets)
requestPartBody("one").document(this.operationBuilder.request("http://localhost") throws IOException {
requestPartBody("one").document(operationBuilder.request("http://localhost")
.part("one", "".getBytes()) .part("one", "".getBytes())
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_PROBLEM_JSON_VALUE) .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_PROBLEM_JSON_VALUE)
.build()); .build());
assertThat(this.generatedSnippets.snippet("request-part-one-body")) assertThat(snippets.requestPartBody("one"))
.is(codeBlock("json", "nowrap").withContent("")); .isCodeBlock((codeBlock) -> codeBlock.withLanguageAndOptions("json", "nowrap").content(""));
} }
@Test @RenderedSnippetTest
public void requestPartWithXmlMediaType() throws IOException { void requestPartWithXmlMediaType(OperationBuilder operationBuilder, AssertableSnippets snippets)
requestPartBody("one").document(this.operationBuilder.request("http://localhost") throws IOException {
requestPartBody("one").document(operationBuilder.request("http://localhost")
.part("one", "".getBytes()) .part("one", "".getBytes())
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_XML_VALUE) .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_XML_VALUE)
.build()); .build());
assertThat(this.generatedSnippets.snippet("request-part-one-body")) assertThat(snippets.requestPartBody("one"))
.is(codeBlock("xml", "nowrap").withContent("")); .isCodeBlock((codeBlock) -> codeBlock.withLanguageAndOptions("xml", "nowrap").content(""));
} }
@Test @RenderedSnippetTest
public void requestPartWithXmlSubtypeMediaType() throws IOException { void requestPartWithXmlSubtypeMediaType(OperationBuilder operationBuilder, AssertableSnippets snippets)
requestPartBody("one").document(this.operationBuilder.request("http://localhost") throws IOException {
requestPartBody("one").document(operationBuilder.request("http://localhost")
.part("one", "".getBytes()) .part("one", "".getBytes())
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_ATOM_XML_VALUE) .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_ATOM_XML_VALUE)
.build()); .build());
assertThat(this.generatedSnippets.snippet("request-part-one-body")) assertThat(snippets.requestPartBody("one"))
.is(codeBlock("xml", "nowrap").withContent("")); .isCodeBlock((codeBlock) -> codeBlock.withLanguageAndOptions("xml", "nowrap").content(""));
} }
@Test @RenderedSnippetTest
public void subsectionOfRequestPartBody() throws IOException { void subsectionOfRequestPartBody(OperationBuilder operationBuilder, AssertableSnippets snippets)
requestPartBody("one", beneathPath("a.b")).document(this.operationBuilder.request("http://localhost") throws IOException {
requestPartBody("one", beneathPath("a.b")).document(operationBuilder.request("http://localhost")
.part("one", "{\"a\":{\"b\":{\"c\":5}}}".getBytes()) .part("one", "{\"a\":{\"b\":{\"c\":5}}}".getBytes())
.build()); .build());
assertThat(this.generatedSnippets.snippet("request-part-one-body-beneath-a.b")) assertThat(snippets.requestPartBody("one", "beneath-a.b"))
.is(codeBlock(null, "nowrap").withContent("{\"c\":5}")); .isCodeBlock((codeBlock) -> codeBlock.withOptions("nowrap").content("{\"c\":5}"));
} }
@Test @RenderedSnippetTest
public void customSnippetAttributes() throws IOException { @SnippetTemplate(snippet = "request-part-body", template = "request-part-body-with-language")
TemplateResourceResolver resolver = mock(TemplateResourceResolver.class); void customSnippetAttributes(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
given(resolver.resolveTemplateResource("request-part-body")) requestPartBody("one", attributes(key("language").value("json")))
.willReturn(snippetResource("request-part-body-with-language")); .document(operationBuilder.request("http://localhost").part("one", "{\"a\":\"alpha\"}".getBytes()).build());
requestPartBody("one", attributes(key("language").value("json"))).document( assertThat(snippets.requestPartBody("one")).isCodeBlock(
this.operationBuilder.attribute(TemplateEngine.class.getName(), new MustacheTemplateEngine(resolver)) (codeBlock) -> codeBlock.withLanguageAndOptions("json", "nowrap").content("{\"a\":\"alpha\"}"));
.request("http://localhost")
.part("one", "{\"a\":\"alpha\"}".getBytes())
.build());
assertThat(this.generatedSnippets.snippet("request-part-one-body"))
.is(codeBlock("json", "nowrap").withContent("{\"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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with 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 java.io.IOException;
import org.junit.Test;
import org.springframework.http.HttpHeaders; import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.restdocs.AbstractSnippetTests; import org.springframework.restdocs.testfixtures.jupiter.AssertableSnippets;
import org.springframework.restdocs.templates.TemplateEngine; import org.springframework.restdocs.testfixtures.jupiter.OperationBuilder;
import org.springframework.restdocs.templates.TemplateFormat; import org.springframework.restdocs.testfixtures.jupiter.RenderedSnippetTest;
import org.springframework.restdocs.templates.TemplateResourceResolver; import org.springframework.restdocs.testfixtures.jupiter.SnippetTemplate;
import org.springframework.restdocs.templates.mustache.MustacheTemplateEngine;
import static org.assertj.core.api.Assertions.assertThat; 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.beneathPath;
import static org.springframework.restdocs.payload.PayloadDocumentation.requestBody; import static org.springframework.restdocs.payload.PayloadDocumentation.requestBody;
import static org.springframework.restdocs.snippet.Attributes.attributes; import static org.springframework.restdocs.snippet.Attributes.attributes;
@@ -41,77 +36,74 @@ import static org.springframework.restdocs.snippet.Attributes.key;
* *
* @author Andy Wilkinson * @author Andy Wilkinson
*/ */
public class RequestBodySnippetTests extends AbstractSnippetTests { class RequestBodySnippetTests {
public RequestBodySnippetTests(String name, TemplateFormat templateFormat) { @RenderedSnippetTest
super(name, templateFormat); 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 @RenderedSnippetTest
public void requestWithBody() throws IOException { void requestWithNoBody(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
requestBody().document(this.operationBuilder.request("http://localhost").content("some content").build()); requestBody().document(operationBuilder.request("http://localhost").build());
assertThat(this.generatedSnippets.snippet("request-body")) assertThat(snippets.requestBody()).isCodeBlock((codeBlock) -> codeBlock.withOptions("nowrap").content(""));
.is(codeBlock(null, "nowrap").withContent("some content"));
} }
@Test @RenderedSnippetTest
public void requestWithNoBody() throws IOException { void requestWithJsonMediaType(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
requestBody().document(this.operationBuilder.request("http://localhost").build()); requestBody().document(operationBuilder.request("http://localhost")
assertThat(this.generatedSnippets.snippet("request-body")).is(codeBlock(null, "nowrap").withContent(""));
}
@Test
public void requestWithJsonMediaType() throws IOException {
requestBody().document(this.operationBuilder.request("http://localhost")
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
.build()); .build());
assertThat(this.generatedSnippets.snippet("request-body")).is(codeBlock("json", "nowrap").withContent("")); assertThat(snippets.requestBody())
.isCodeBlock((codeBlock) -> codeBlock.withLanguageAndOptions("json", "nowrap").content(""));
} }
@Test @RenderedSnippetTest
public void requestWithJsonSubtypeMediaType() throws IOException { void requestWithJsonSubtypeMediaType(OperationBuilder operationBuilder, AssertableSnippets snippets)
requestBody().document(this.operationBuilder.request("http://localhost") throws IOException {
requestBody().document(operationBuilder.request("http://localhost")
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_PROBLEM_JSON_VALUE) .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_PROBLEM_JSON_VALUE)
.build()); .build());
assertThat(this.generatedSnippets.snippet("request-body")).is(codeBlock("json", "nowrap").withContent("")); assertThat(snippets.requestBody())
.isCodeBlock((codeBlock) -> codeBlock.withLanguageAndOptions("json", "nowrap").content(""));
} }
@Test @RenderedSnippetTest
public void requestWithXmlMediaType() throws IOException { void requestWithXmlMediaType(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
requestBody().document(this.operationBuilder.request("http://localhost") requestBody().document(operationBuilder.request("http://localhost")
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_XML_VALUE) .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_XML_VALUE)
.build()); .build());
assertThat(this.generatedSnippets.snippet("request-body")).is(codeBlock("xml", "nowrap").withContent("")); assertThat(snippets.requestBody())
.isCodeBlock((codeBlock) -> codeBlock.withLanguageAndOptions("xml", "nowrap").content(""));
} }
@Test @RenderedSnippetTest
public void requestWithXmlSubtypeMediaType() throws IOException { void requestWithXmlSubtypeMediaType(OperationBuilder operationBuilder, AssertableSnippets snippets)
requestBody().document(this.operationBuilder.request("http://localhost") throws IOException {
requestBody().document(operationBuilder.request("http://localhost")
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_ATOM_XML_VALUE) .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_ATOM_XML_VALUE)
.build()); .build());
assertThat(this.generatedSnippets.snippet("request-body")).is(codeBlock("xml", "nowrap").withContent("")); assertThat(snippets.requestBody())
.isCodeBlock((codeBlock) -> codeBlock.withLanguageAndOptions("xml", "nowrap").content(""));
} }
@Test @RenderedSnippetTest
public void subsectionOfRequestBody() throws IOException { void subsectionOfRequestBody(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
requestBody(beneathPath("a.b")) requestBody(beneathPath("a.b"))
.document(this.operationBuilder.request("http://localhost").content("{\"a\":{\"b\":{\"c\":5}}}").build()); .document(operationBuilder.request("http://localhost").content("{\"a\":{\"b\":{\"c\":5}}}").build());
assertThat(this.generatedSnippets.snippet("request-body-beneath-a.b")) assertThat(snippets.requestBody("beneath-a.b"))
.is(codeBlock(null, "nowrap").withContent("{\"c\":5}")); .isCodeBlock((codeBlock) -> codeBlock.withOptions("nowrap").content("{\"c\":5}"));
} }
@Test @RenderedSnippetTest
public void customSnippetAttributes() throws IOException { @SnippetTemplate(snippet = "request-body", template = "request-body-with-language")
TemplateResourceResolver resolver = mock(TemplateResourceResolver.class); void customSnippetAttributes(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
given(resolver.resolveTemplateResource("request-body")) requestBody(attributes(key("language").value("json")))
.willReturn(snippetResource("request-body-with-language")); .document(operationBuilder.request("http://localhost").content("{\"a\":\"alpha\"}").build());
requestBody(attributes(key("language").value("json"))).document( assertThat(snippets.requestBody()).isCodeBlock(
this.operationBuilder.attribute(TemplateEngine.class.getName(), new MustacheTemplateEngine(resolver)) (codeBlock) -> codeBlock.withLanguageAndOptions("json", "nowrap").content("{\"a\":\"alpha\"}"));
.request("http://localhost")
.content("{\"a\":\"alpha\"}")
.build());
assertThat(this.generatedSnippets.snippet("request-body"))
.is(codeBlock("json", "nowrap").withContent("{\"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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with 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.io.IOException;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections;
import org.junit.Test;
import org.springframework.http.HttpHeaders; import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.restdocs.AbstractSnippetTests; import org.springframework.restdocs.snippet.SnippetException;
import org.springframework.restdocs.templates.TemplateEngine; import org.springframework.restdocs.testfixtures.jupiter.AssertableSnippets;
import org.springframework.restdocs.templates.TemplateFormat; import org.springframework.restdocs.testfixtures.jupiter.OperationBuilder;
import org.springframework.restdocs.templates.TemplateFormats; import org.springframework.restdocs.testfixtures.jupiter.RenderedSnippetTest;
import org.springframework.restdocs.templates.TemplateResourceResolver; import org.springframework.restdocs.testfixtures.jupiter.SnippetTemplate;
import org.springframework.restdocs.templates.mustache.MustacheTemplateEngine; import org.springframework.restdocs.testfixtures.jupiter.SnippetTest;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.BDDMockito.given; import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.mockito.Mockito.mock;
import static org.springframework.restdocs.payload.PayloadDocumentation.beneathPath; import static org.springframework.restdocs.payload.PayloadDocumentation.beneathPath;
import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath; import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath;
import static org.springframework.restdocs.payload.PayloadDocumentation.requestFields; import static org.springframework.restdocs.payload.PayloadDocumentation.requestFields;
@@ -46,342 +44,492 @@ import static org.springframework.restdocs.snippet.Attributes.key;
* @author Andy Wilkinson * @author Andy Wilkinson
* @author Sungjun Lee * @author Sungjun Lee
*/ */
public class RequestFieldsSnippetTests extends AbstractSnippetTests { class RequestFieldsSnippetTests {
public RequestFieldsSnippetTests(String name, TemplateFormat templateFormat) { @RenderedSnippetTest
super(name, templateFormat); void mapRequestWithFields(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
}
@Test
public void mapRequestWithFields() throws IOException {
new RequestFieldsSnippet(Arrays.asList(fieldWithPath("a.b").description("one"), new RequestFieldsSnippet(Arrays.asList(fieldWithPath("a.b").description("one"),
fieldWithPath("a.c").description("two"), fieldWithPath("a").description("three"))) 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\"}}") .content("{\"a\": {\"b\": 5, \"c\": \"charlie\"}}")
.build()); .build());
assertThat(this.generatedSnippets.requestFields()) assertThat(snippets.requestFields()).isTable((table) -> table.withHeader("Path", "Type", "Description")
.is(tableWithHeader("Path", "Type", "Description").row("`a.b`", "`Number`", "one") .row("`a.b`", "`Number`", "one")
.row("`a.c`", "`String`", "two") .row("`a.c`", "`String`", "two")
.row("`a`", "`Object`", "three")); .row("`a`", "`Object`", "three"));
} }
@Test @RenderedSnippetTest
public void mapRequestWithNullField() throws IOException { void mapRequestWithNullField(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
new RequestFieldsSnippet(Arrays.asList(fieldWithPath("a.b").description("one"))) new RequestFieldsSnippet(Arrays.asList(fieldWithPath("a.b").description("one")))
.document(this.operationBuilder.request("http://localhost").content("{\"a\": {\"b\": null}}").build()); .document(operationBuilder.request("http://localhost").content("{\"a\": {\"b\": null}}").build());
assertThat(this.generatedSnippets.requestFields()) assertThat(snippets.requestFields())
.is(tableWithHeader("Path", "Type", "Description").row("`a.b`", "`Null`", "one")); .isTable((table) -> table.withHeader("Path", "Type", "Description").row("`a.b`", "`Null`", "one"));
} }
@Test @RenderedSnippetTest
public void entireSubsectionsCanBeDocumented() throws IOException { void entireSubsectionsCanBeDocumented(OperationBuilder operationBuilder, AssertableSnippets snippets)
throws IOException {
new RequestFieldsSnippet(Arrays.asList(subsectionWithPath("a").description("one"))) 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\"}}") .content("{\"a\": {\"b\": 5, \"c\": \"charlie\"}}")
.build()); .build());
assertThat(this.generatedSnippets.requestFields()) assertThat(snippets.requestFields())
.is(tableWithHeader("Path", "Type", "Description").row("`a`", "`Object`", "one")); .isTable((table) -> table.withHeader("Path", "Type", "Description").row("`a`", "`Object`", "one"));
} }
@Test @RenderedSnippetTest
public void subsectionOfMapRequest() throws IOException { void subsectionOfMapRequest(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
requestFields(beneathPath("a"), fieldWithPath("b").description("one"), fieldWithPath("c").description("two")) 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\"}}") .content("{\"a\": {\"b\": 5, \"c\": \"charlie\"}}")
.build()); .build());
assertThat(this.generatedSnippets.snippet("request-fields-beneath-a")) assertThat(snippets.requestFields("beneath-a"))
.is(tableWithHeader("Path", "Type", "Description").row("`b`", "`Number`", "one") .isTable((table) -> table.withHeader("Path", "Type", "Description")
.row("`b`", "`Number`", "one")
.row("`c`", "`String`", "two")); .row("`c`", "`String`", "two"));
} }
@Test @RenderedSnippetTest
public void subsectionOfMapRequestWithCommonPrefix() throws IOException { void subsectionOfMapRequestWithCommonPrefix(OperationBuilder operationBuilder, AssertableSnippets snippets)
throws IOException {
requestFields(beneathPath("a")).andWithPrefix("b.", fieldWithPath("c").description("two")) 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\"}}}") .content("{\"a\": {\"b\": {\"c\": \"charlie\"}}}")
.build()); .build());
assertThat(this.generatedSnippets.snippet("request-fields-beneath-a")) assertThat(snippets.requestFields("beneath-a"))
.is(tableWithHeader("Path", "Type", "Description").row("`b.c`", "`String`", "two")); .isTable((table) -> table.withHeader("Path", "Type", "Description").row("`b.c`", "`String`", "two"));
} }
@Test @RenderedSnippetTest
public void arrayRequestWithFields() throws IOException { void arrayRequestWithFields(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
new RequestFieldsSnippet( new RequestFieldsSnippet(
Arrays.asList(fieldWithPath("[]").description("one"), fieldWithPath("[]a.b").description("two"), Arrays.asList(fieldWithPath("[]").description("one"), fieldWithPath("[]a.b").description("two"),
fieldWithPath("[]a.c").description("three"), fieldWithPath("[]a").description("four"))) 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\"}}]") .content("[{\"a\": {\"b\": 5, \"c\":\"charlie\"}}," + "{\"a\": {\"b\": 4, \"c\":\"chalk\"}}]")
.build()); .build());
assertThat(this.generatedSnippets.requestFields()) assertThat(snippets.requestFields()).isTable((table) -> table.withHeader("Path", "Type", "Description")
.is(tableWithHeader("Path", "Type", "Description").row("`[]`", "`Array`", "one") .row("`[]`", "`Array`", "one")
.row("`[]a.b`", "`Number`", "two") .row("`[]a.b`", "`Number`", "two")
.row("`[]a.c`", "`String`", "three") .row("`[]a.c`", "`String`", "three")
.row("`[]a`", "`Object`", "four")); .row("`[]a`", "`Object`", "four"));
} }
@Test @RenderedSnippetTest
public void arrayRequestWithAlwaysNullField() throws IOException { void arrayRequestWithAlwaysNullField(OperationBuilder operationBuilder, AssertableSnippets snippets)
throws IOException {
new RequestFieldsSnippet(Arrays.asList(fieldWithPath("[]a.b").description("one"))) 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}}]") .content("[{\"a\": {\"b\": null}}," + "{\"a\": {\"b\": null}}]")
.build()); .build());
assertThat(this.generatedSnippets.requestFields()) assertThat(snippets.requestFields())
.is(tableWithHeader("Path", "Type", "Description").row("`[]a.b`", "`Null`", "one")); .isTable((table) -> table.withHeader("Path", "Type", "Description").row("`[]a.b`", "`Null`", "one"));
} }
@Test @RenderedSnippetTest
public void subsectionOfArrayRequest() throws IOException { void subsectionOfArrayRequest(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
requestFields(beneathPath("[].a"), fieldWithPath("b").description("one"), fieldWithPath("c").description("two")) 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\"}}]") .content("[{\"a\": {\"b\": 5, \"c\": \"charlie\"}}]")
.build()); .build());
assertThat(this.generatedSnippets.snippet("request-fields-beneath-[].a")) assertThat(snippets.requestFields("beneath-[].a"))
.is(tableWithHeader("Path", "Type", "Description").row("`b`", "`Number`", "one") .isTable((table) -> table.withHeader("Path", "Type", "Description")
.row("`b`", "`Number`", "one")
.row("`c`", "`String`", "two")); .row("`c`", "`String`", "two"));
} }
@Test @RenderedSnippetTest
public void ignoredRequestField() throws IOException { void ignoredRequestField(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
new RequestFieldsSnippet(Arrays.asList(fieldWithPath("a").ignored(), fieldWithPath("b").description("Field b"))) new RequestFieldsSnippet(Arrays.asList(fieldWithPath("a").ignored(), fieldWithPath("b").description("Field b")))
.document(this.operationBuilder.request("http://localhost").content("{\"a\": 5, \"b\": 4}").build()); .document(operationBuilder.request("http://localhost").content("{\"a\": 5, \"b\": 4}").build());
assertThat(this.generatedSnippets.requestFields()) assertThat(snippets.requestFields())
.is(tableWithHeader("Path", "Type", "Description").row("`b`", "`Number`", "Field b")); .isTable((table) -> table.withHeader("Path", "Type", "Description").row("`b`", "`Number`", "Field b"));
} }
@Test @RenderedSnippetTest
public void entireSubsectionCanBeIgnored() throws IOException { void entireSubsectionCanBeIgnored(OperationBuilder operationBuilder, AssertableSnippets snippets)
throws IOException {
new RequestFieldsSnippet( new RequestFieldsSnippet(
Arrays.asList(subsectionWithPath("a").ignored(), fieldWithPath("c").description("Field c"))) Arrays.asList(subsectionWithPath("a").ignored(), fieldWithPath("c").description("Field c")))
.document( .document(operationBuilder.request("http://localhost").content("{\"a\": {\"b\": 5}, \"c\": 4}").build());
this.operationBuilder.request("http://localhost").content("{\"a\": {\"b\": 5}, \"c\": 4}").build()); assertThat(snippets.requestFields())
assertThat(this.generatedSnippets.requestFields()) .isTable((table) -> table.withHeader("Path", "Type", "Description").row("`c`", "`Number`", "Field c"));
.is(tableWithHeader("Path", "Type", "Description").row("`c`", "`Number`", "Field c"));
} }
@Test @RenderedSnippetTest
public void allUndocumentedRequestFieldsCanBeIgnored() throws IOException { void allUndocumentedRequestFieldsCanBeIgnored(OperationBuilder operationBuilder, AssertableSnippets snippets)
throws IOException {
new RequestFieldsSnippet(Arrays.asList(fieldWithPath("b").description("Field b")), true) new RequestFieldsSnippet(Arrays.asList(fieldWithPath("b").description("Field b")), true)
.document(this.operationBuilder.request("http://localhost").content("{\"a\": 5, \"b\": 4}").build()); .document(operationBuilder.request("http://localhost").content("{\"a\": 5, \"b\": 4}").build());
assertThat(this.generatedSnippets.requestFields()) assertThat(snippets.requestFields())
.is(tableWithHeader("Path", "Type", "Description").row("`b`", "`Number`", "Field b")); .isTable((table) -> table.withHeader("Path", "Type", "Description").row("`b`", "`Number`", "Field b"));
} }
@Test @RenderedSnippetTest
public void allUndocumentedFieldsContinueToBeIgnoredAfterAddingDescriptors() throws IOException { void allUndocumentedFieldsContinueToBeIgnoredAfterAddingDescriptors(OperationBuilder operationBuilder,
AssertableSnippets snippets) throws IOException {
new RequestFieldsSnippet(Arrays.asList(fieldWithPath("b").description("Field b")), true) new RequestFieldsSnippet(Arrays.asList(fieldWithPath("b").description("Field b")), true)
.andWithPrefix("c.", fieldWithPath("d").description("Field d")) .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( .document(
this.operationBuilder.request("http://localhost").content("{\"a\": { \"b\": \"bravo\"}}").build()); operationBuilder.request("http://localhost").content("{\"a\":5,\"b\":4,\"c\":{\"d\": 3}}").build());
assertThat(this.generatedSnippets.requestFields()) assertThat(snippets.requestFields()).isTable((table) -> table.withHeader("Path", "Type", "Description")
.is(tableWithHeader("Path", "Type", "Description").row("`a.b`", "`String`", "one")); .row("`b`", "`Number`", "Field b")
.row("`c.d`", "`Number`", "Field d"));
} }
@Test @RenderedSnippetTest
public void requestFieldsWithCustomAttributes() throws IOException { void missingOptionalRequestField(OperationBuilder operationBuilder, AssertableSnippets snippets)
TemplateResourceResolver resolver = mock(TemplateResourceResolver.class); throws IOException {
given(resolver.resolveTemplateResource("request-fields")) new RequestFieldsSnippet(
.willReturn(snippetResource("request-fields-with-title")); 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")), new RequestFieldsSnippet(Arrays.asList(fieldWithPath("a").description("one")),
attributes(key("title").value("Custom title"))) attributes(key("title").value("Custom title")))
.document(this.operationBuilder .document(operationBuilder.request("http://localhost").content("{\"a\": \"foo\"}").build());
.attribute(TemplateEngine.class.getName(), new MustacheTemplateEngine(resolver)) assertThat(snippets.requestFields())
.request("http://localhost") .isTable((table) -> table.withTitleAndHeader("Custom title", "Path", "Type", "Description")
.content("{\"a\": \"foo\"}") .row("a", "String", "one"));
.build());
assertThat(this.generatedSnippets.requestFields()).contains("Custom title");
} }
@Test @RenderedSnippetTest
public void requestFieldsWithCustomDescriptorAttributes() throws IOException { @SnippetTemplate(snippet = "request-fields", template = "request-fields-with-extra-column")
TemplateResourceResolver resolver = mock(TemplateResourceResolver.class); void requestFieldsWithCustomDescriptorAttributes(OperationBuilder operationBuilder, AssertableSnippets snippets)
given(resolver.resolveTemplateResource("request-fields")) throws IOException {
.willReturn(snippetResource("request-fields-with-extra-column"));
new RequestFieldsSnippet( new RequestFieldsSnippet(
Arrays.asList(fieldWithPath("a.b").description("one").attributes(key("foo").value("alpha")), Arrays.asList(fieldWithPath("a.b").description("one").attributes(key("foo").value("alpha")),
fieldWithPath("a.c").description("two").attributes(key("foo").value("bravo")), fieldWithPath("a.c").description("two").attributes(key("foo").value("bravo")),
fieldWithPath("a").description("three").attributes(key("foo").value("charlie")))) fieldWithPath("a").description("three").attributes(key("foo").value("charlie"))))
.document(this.operationBuilder .document(operationBuilder.request("http://localhost")
.attribute(TemplateEngine.class.getName(), new MustacheTemplateEngine(resolver))
.request("http://localhost")
.content("{\"a\": {\"b\": 5, \"c\": \"charlie\"}}") .content("{\"a\": {\"b\": 5, \"c\": \"charlie\"}}")
.build()); .build());
assertThat(this.generatedSnippets.requestFields()) assertThat(snippets.requestFields()).isTable((table) -> table.withHeader("Path", "Type", "Description", "Foo")
.is(tableWithHeader("Path", "Type", "Description", "Foo").row("a.b", "Number", "one", "alpha") .row("a.b", "Number", "one", "alpha")
.row("a.c", "String", "two", "bravo") .row("a.c", "String", "two", "bravo")
.row("a", "Object", "three", "charlie")); .row("a", "Object", "three", "charlie"));
} }
@Test @RenderedSnippetTest
public void fieldWithExplicitExactlyMatchingType() throws IOException { void fieldWithExplicitExactlyMatchingType(OperationBuilder operationBuilder, AssertableSnippets snippets)
throws IOException {
new RequestFieldsSnippet(Arrays.asList(fieldWithPath("a").description("one").type(JsonFieldType.NUMBER))) new RequestFieldsSnippet(Arrays.asList(fieldWithPath("a").description("one").type(JsonFieldType.NUMBER)))
.document(this.operationBuilder.request("http://localhost").content("{\"a\": 5 }").build()); .document(operationBuilder.request("http://localhost").content("{\"a\": 5 }").build());
assertThat(this.generatedSnippets.requestFields()) assertThat(snippets.requestFields())
.is(tableWithHeader("Path", "Type", "Description").row("`a`", "`Number`", "one")); .isTable((table) -> table.withHeader("Path", "Type", "Description").row("`a`", "`Number`", "one"));
} }
@Test @RenderedSnippetTest
public void fieldWithExplicitVariesType() throws IOException { void fieldWithExplicitVariesType(OperationBuilder operationBuilder, AssertableSnippets snippets)
throws IOException {
new RequestFieldsSnippet(Arrays.asList(fieldWithPath("a").description("one").type(JsonFieldType.VARIES))) new RequestFieldsSnippet(Arrays.asList(fieldWithPath("a").description("one").type(JsonFieldType.VARIES)))
.document(this.operationBuilder.request("http://localhost").content("{\"a\": 5 }").build()); .document(operationBuilder.request("http://localhost").content("{\"a\": 5 }").build());
assertThat(this.generatedSnippets.requestFields()) assertThat(snippets.requestFields())
.is(tableWithHeader("Path", "Type", "Description").row("`a`", "`Varies`", "one")); .isTable((table) -> table.withHeader("Path", "Type", "Description").row("`a`", "`Varies`", "one"));
} }
@Test @RenderedSnippetTest
public void applicationXmlRequestFields() throws IOException { void applicationXmlRequestFields(OperationBuilder operationBuilder, AssertableSnippets snippets)
xmlRequestFields(MediaType.APPLICATION_XML); throws IOException {
xmlRequestFields(MediaType.APPLICATION_XML, operationBuilder, snippets);
} }
@Test @RenderedSnippetTest
public void textXmlRequestFields() throws IOException { void textXmlRequestFields(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
xmlRequestFields(MediaType.TEXT_XML); xmlRequestFields(MediaType.TEXT_XML, operationBuilder, snippets);
} }
@Test @RenderedSnippetTest
public void customXmlRequestFields() throws IOException { void customXmlRequestFields(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
xmlRequestFields(MediaType.parseMediaType("application/vnd.com.example+xml")); 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"), new RequestFieldsSnippet(Arrays.asList(fieldWithPath("a/b").description("one").type("b"),
fieldWithPath("a/c").description("two").type("c"), fieldWithPath("a").description("three").type("a"))) 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>") .content("<a><b>5</b><c>charlie</c></a>")
.header(HttpHeaders.CONTENT_TYPE, contentType.toString()) .header(HttpHeaders.CONTENT_TYPE, contentType.toString())
.build()); .build());
assertThat(this.generatedSnippets.requestFields()) assertThat(snippets.requestFields()).isTable((table) -> table.withHeader("Path", "Type", "Description")
.is(tableWithHeader("Path", "Type", "Description").row("`a/b`", "`b`", "one") .row("`a/b`", "`b`", "one")
.row("`a/c`", "`c`", "two") .row("`a/c`", "`c`", "two")
.row("`a`", "`a`", "three")); .row("`a`", "`a`", "three"));
} }
@Test @RenderedSnippetTest
public void entireSubsectionOfXmlPayloadCanBeDocumented() throws IOException { void entireSubsectionOfXmlPayloadCanBeDocumented(OperationBuilder operationBuilder, AssertableSnippets snippets)
throws IOException {
new RequestFieldsSnippet(Arrays.asList(subsectionWithPath("a").description("one").type("a"))) 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>") .content("<a><b>5</b><c>charlie</c></a>")
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_XML_VALUE) .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_XML_VALUE)
.build()); .build());
assertThat(this.generatedSnippets.requestFields()) assertThat(snippets.requestFields())
.is(tableWithHeader("Path", "Type", "Description").row("`a`", "`a`", "one")); .isTable((table) -> table.withHeader("Path", "Type", "Description").row("`a`", "`a`", "one"));
} }
@Test @RenderedSnippetTest
public void additionalDescriptors() throws IOException { void additionalDescriptors(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
PayloadDocumentation PayloadDocumentation
.requestFields(fieldWithPath("a.b").description("one"), fieldWithPath("a.c").description("two")) .requestFields(fieldWithPath("a.b").description("one"), fieldWithPath("a.c").description("two"))
.and(fieldWithPath("a").description("three")) .and(fieldWithPath("a").description("three"))
.document(this.operationBuilder.request("http://localhost") .document(operationBuilder.request("http://localhost")
.content("{\"a\": {\"b\": 5, \"c\": \"charlie\"}}") .content("{\"a\": {\"b\": 5, \"c\": \"charlie\"}}")
.build()); .build());
assertThat(this.generatedSnippets.requestFields()) assertThat(snippets.requestFields()).isTable((table) -> table.withHeader("Path", "Type", "Description")
.is(tableWithHeader("Path", "Type", "Description").row("`a.b`", "`Number`", "one") .row("`a.b`", "`Number`", "one")
.row("`a.c`", "`String`", "two") .row("`a.c`", "`String`", "two")
.row("`a`", "`Object`", "three")); .row("`a`", "`Object`", "three"));
} }
@Test @RenderedSnippetTest
public void prefixedAdditionalDescriptors() throws IOException { void prefixedAdditionalDescriptors(OperationBuilder operationBuilder, AssertableSnippets snippets)
throws IOException {
PayloadDocumentation.requestFields(fieldWithPath("a").description("one")) PayloadDocumentation.requestFields(fieldWithPath("a").description("one"))
.andWithPrefix("a.", fieldWithPath("b").description("two"), fieldWithPath("c").description("three")) .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\"}}") .content("{\"a\": {\"b\": 5, \"c\": \"charlie\"}}")
.build()); .build());
assertThat(this.generatedSnippets.requestFields()) assertThat(snippets.requestFields()).isTable((table) -> table.withHeader("Path", "Type", "Description")
.is(tableWithHeader("Path", "Type", "Description").row("`a`", "`Object`", "one") .row("`a`", "`Object`", "one")
.row("`a.b`", "`Number`", "two") .row("`a.b`", "`Number`", "two")
.row("`a.c`", "`String`", "three")); .row("`a.c`", "`String`", "three"));
} }
@Test @RenderedSnippetTest
public void requestWithFieldsWithEscapedContent() throws IOException { void requestWithFieldsWithEscapedContent(OperationBuilder operationBuilder, AssertableSnippets snippets)
throws IOException {
new RequestFieldsSnippet(Arrays.asList(fieldWithPath("Foo|Bar").type("one|two").description("three|four"))) new RequestFieldsSnippet(Arrays.asList(fieldWithPath("Foo|Bar").type("one|two").description("three|four")))
.document(this.operationBuilder.request("http://localhost").content("{\"Foo|Bar\": 5}").build()); .document(operationBuilder.request("http://localhost").content("{\"Foo|Bar\": 5}").build());
assertThat(this.generatedSnippets.requestFields()).is(tableWithHeader("Path", "Type", "Description") assertThat(snippets.requestFields()).isTable(
.row(escapeIfNecessary("`Foo|Bar`"), escapeIfNecessary("`one|two`"), escapeIfNecessary("three|four"))); (table) -> table.withHeader("Path", "Type", "Description").row("`Foo|Bar`", "`one|two`", "three|four"));
} }
@Test @RenderedSnippetTest
public void mapRequestWithVaryingKeysMatchedUsingWildcard() throws IOException { void mapRequestWithVaryingKeysMatchedUsingWildcard(OperationBuilder operationBuilder, AssertableSnippets snippets)
throws IOException {
new RequestFieldsSnippet(Arrays.asList(fieldWithPath("things.*.size").description("one"), new RequestFieldsSnippet(Arrays.asList(fieldWithPath("things.*.size").description("one"),
fieldWithPath("things.*.type").description("two"))) fieldWithPath("things.*.type").description("two")))
.document(this.operationBuilder.request("http://localhost") .document(operationBuilder.request("http://localhost")
.content("{\"things\": {\"12abf\": {\"type\":" + "\"Whale\", \"size\": \"HUGE\"}," .content("{\"things\": {\"12abf\": {\"type\":" + "\"Whale\", \"size\": \"HUGE\"},"
+ "\"gzM33\" : {\"type\": \"Screw\"," + "\"size\": \"SMALL\"}}}") + "\"gzM33\" : {\"type\": \"Screw\"," + "\"size\": \"SMALL\"}}}")
.build()); .build());
assertThat(this.generatedSnippets.requestFields()) assertThat(snippets.requestFields()).isTable((table) -> table.withHeader("Path", "Type", "Description")
.is(tableWithHeader("Path", "Type", "Description").row("`things.*.size`", "`String`", "one") .row("`things.*.size`", "`String`", "one")
.row("`things.*.type`", "`String`", "two")); .row("`things.*.type`", "`String`", "two"));
} }
@Test @RenderedSnippetTest
public void requestWithArrayContainingFieldThatIsSometimesNull() throws IOException { void requestWithArrayContainingFieldThatIsSometimesNull(OperationBuilder operationBuilder,
AssertableSnippets snippets) throws IOException {
new RequestFieldsSnippet( new RequestFieldsSnippet(
Arrays.asList(fieldWithPath("assets[].name").description("one").type(JsonFieldType.STRING).optional())) 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}, " .content("{\"assets\": [" + "{\"name\": \"sample1\"}, " + "{\"name\": null}, "
+ "{\"name\": \"sample2\"}]}") + "{\"name\": \"sample2\"}]}")
.build()); .build());
assertThat(this.generatedSnippets.requestFields()) assertThat(snippets.requestFields()).isTable(
.is(tableWithHeader("Path", "Type", "Description").row("`assets[].name`", "`String`", "one")); (table) -> table.withHeader("Path", "Type", "Description").row("`assets[].name`", "`String`", "one"));
} }
@Test @RenderedSnippetTest
public void optionalFieldBeneathArrayThatIsSometimesAbsent() throws IOException { void optionalFieldBeneathArrayThatIsSometimesAbsent(OperationBuilder operationBuilder, AssertableSnippets snippets)
throws IOException {
new RequestFieldsSnippet( new RequestFieldsSnippet(
Arrays.asList(fieldWithPath("a[].b").description("one").type(JsonFieldType.NUMBER).optional(), Arrays.asList(fieldWithPath("a[].b").description("one").type(JsonFieldType.NUMBER).optional(),
fieldWithPath("a[].c").description("two").type(JsonFieldType.NUMBER))) 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}]}") .content("{\"a\":[{\"b\": 1,\"c\": 2}, " + "{\"c\": 2}, {\"b\": 1,\"c\": 2}]}")
.build()); .build());
assertThat(this.generatedSnippets.requestFields()) assertThat(snippets.requestFields()).isTable((table) -> table.withHeader("Path", "Type", "Description")
.is(tableWithHeader("Path", "Type", "Description").row("`a[].b`", "`Number`", "one") .row("`a[].b`", "`Number`", "one")
.row("`a[].c`", "`Number`", "two")); .row("`a[].c`", "`Number`", "two"));
} }
@Test @RenderedSnippetTest
public void typeDeterminationDoesNotSetTypeOnDescriptor() throws IOException { void typeDeterminationDoesNotSetTypeOnDescriptor(OperationBuilder operationBuilder, AssertableSnippets snippets)
throws IOException {
FieldDescriptor descriptor = fieldWithPath("a.b").description("one"); FieldDescriptor descriptor = fieldWithPath("a.b").description("one");
new RequestFieldsSnippet(Arrays.asList(descriptor)) 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(descriptor.getType()).isNull();
assertThat(this.generatedSnippets.requestFields()) assertThat(snippets.requestFields())
.is(tableWithHeader("Path", "Type", "Description").row("`a.b`", "`Number`", "one")); .isTable((table) -> table.withHeader("Path", "Type", "Description").row("`a.b`", "`Number`", "one"));
} }
private String escapeIfNecessary(String input) { @SnippetTest
if (this.templateFormat.getId().equals(TemplateFormats.markdown().getId())) { void undocumentedRequestField(OperationBuilder operationBuilder) {
return input; assertThatExceptionOfType(SnippetException.class)
} .isThrownBy(() -> new RequestFieldsSnippet(Collections.<FieldDescriptor>emptyList())
return input.replace("|", "\\|"); .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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with 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.Arrays;
import java.util.Collections; import java.util.Collections;
import org.junit.Test;
import org.springframework.restdocs.AbstractSnippetTests;
import org.springframework.restdocs.operation.Operation; 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.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.springframework.restdocs.payload.PayloadDocumentation.beneathPath; import static org.springframework.restdocs.payload.PayloadDocumentation.beneathPath;
import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath; import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath;
@@ -36,86 +38,110 @@ import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWit
* @author Mathieu Pousse * @author Mathieu Pousse
* @author Andy Wilkinson * @author Andy Wilkinson
*/ */
public class RequestPartFieldsSnippetTests extends AbstractSnippetTests { public class RequestPartFieldsSnippetTests {
public RequestPartFieldsSnippetTests(String name, TemplateFormat templateFormat) { @RenderedSnippetTest
super(name, templateFormat); void mapRequestPartFields(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
}
@Test
public void mapRequestPartFields() throws IOException {
new RequestPartFieldsSnippet("one", new RequestPartFieldsSnippet("one",
Arrays.asList(fieldWithPath("a.b").description("one"), fieldWithPath("a.c").description("two"), Arrays.asList(fieldWithPath("a.b").description("one"), fieldWithPath("a.c").description("two"),
fieldWithPath("a").description("three"))) fieldWithPath("a").description("three")))
.document(this.operationBuilder.request("http://localhost") .document(operationBuilder.request("http://localhost")
.part("one", "{\"a\": {\"b\": 5, \"c\": \"charlie\"}}".getBytes()) .part("one", "{\"a\": {\"b\": 5, \"c\": \"charlie\"}}".getBytes())
.build()); .build());
assertThat(this.generatedSnippets.requestPartFields("one")) assertThat(snippets.requestPartFields("one")).isTable((table) -> table.withHeader("Path", "Type", "Description")
.is(tableWithHeader("Path", "Type", "Description").row("`a.b`", "`Number`", "one") .row("`a.b`", "`Number`", "one")
.row("`a.c`", "`String`", "two") .row("`a.c`", "`String`", "two")
.row("`a`", "`Object`", "three")); .row("`a`", "`Object`", "three"));
} }
@Test @RenderedSnippetTest
public void mapRequestPartSubsectionFields() throws IOException { void mapRequestPartSubsectionFields(OperationBuilder operationBuilder, AssertableSnippets snippets)
throws IOException {
new RequestPartFieldsSnippet("one", beneathPath("a"), new RequestPartFieldsSnippet("one", beneathPath("a"),
Arrays.asList(fieldWithPath("b").description("one"), fieldWithPath("c").description("two"))) 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()) .part("one", "{\"a\": {\"b\": 5, \"c\": \"charlie\"}}".getBytes())
.build()); .build());
assertThat(this.generatedSnippets.snippet("request-part-one-fields-beneath-a")) assertThat(snippets.requestPartFields("one", "beneath-a"))
.is(tableWithHeader("Path", "Type", "Description").row("`b`", "`Number`", "one") .isTable((table) -> table.withHeader("Path", "Type", "Description")
.row("`b`", "`Number`", "one")
.row("`c`", "`String`", "two")); .row("`c`", "`String`", "two"));
} }
@Test @RenderedSnippetTest
public void multipleRequestParts() throws IOException { void multipleRequestParts(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
Operation operation = this.operationBuilder.request("http://localhost") Operation operation = operationBuilder.request("http://localhost")
.part("one", "{}".getBytes()) .part("one", "{}".getBytes())
.and() .and()
.part("two", "{}".getBytes()) .part("two", "{}".getBytes())
.build(); .build();
new RequestPartFieldsSnippet("one", Collections.<FieldDescriptor>emptyList()).document(operation); new RequestPartFieldsSnippet("one", Collections.<FieldDescriptor>emptyList()).document(operation);
new RequestPartFieldsSnippet("two", Collections.<FieldDescriptor>emptyList()).document(operation); new RequestPartFieldsSnippet("two", Collections.<FieldDescriptor>emptyList()).document(operation);
assertThat(this.generatedSnippets.requestPartFields("one")).isNotNull(); assertThat(snippets.requestPartFields("one")).isNotNull();
assertThat(this.generatedSnippets.requestPartFields("two")).isNotNull(); assertThat(snippets.requestPartFields("two")).isNotNull();
} }
@Test @RenderedSnippetTest
public void allUndocumentedRequestPartFieldsCanBeIgnored() throws IOException { void allUndocumentedRequestPartFieldsCanBeIgnored(OperationBuilder operationBuilder, AssertableSnippets snippets)
new RequestPartFieldsSnippet("one", Arrays.asList(fieldWithPath("b").description("Field b")), true) throws IOException {
.document(this.operationBuilder.request("http://localhost") new RequestPartFieldsSnippet("one", Arrays.asList(fieldWithPath("b").description("Field b")), true).document(
.part("one", "{\"a\": 5, \"b\": 4}".getBytes()) operationBuilder.request("http://localhost").part("one", "{\"a\": 5, \"b\": 4}".getBytes()).build());
.build()); assertThat(snippets.requestPartFields("one"))
assertThat(this.generatedSnippets.requestPartFields("one")) .isTable((table) -> table.withHeader("Path", "Type", "Description").row("`b`", "`Number`", "Field b"));
.is(tableWithHeader("Path", "Type", "Description").row("`b`", "`Number`", "Field b"));
} }
@Test @RenderedSnippetTest
public void additionalDescriptors() throws IOException { void additionalDescriptors(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
PayloadDocumentation PayloadDocumentation
.requestPartFields("one", fieldWithPath("a.b").description("one"), fieldWithPath("a.c").description("two")) .requestPartFields("one", fieldWithPath("a.b").description("one"), fieldWithPath("a.c").description("two"))
.and(fieldWithPath("a").description("three")) .and(fieldWithPath("a").description("three"))
.document(this.operationBuilder.request("http://localhost") .document(operationBuilder.request("http://localhost")
.part("one", "{\"a\": {\"b\": 5, \"c\": \"charlie\"}}".getBytes()) .part("one", "{\"a\": {\"b\": 5, \"c\": \"charlie\"}}".getBytes())
.build()); .build());
assertThat(this.generatedSnippets.requestPartFields("one")) assertThat(snippets.requestPartFields("one")).isTable((table) -> table.withHeader("Path", "Type", "Description")
.is(tableWithHeader("Path", "Type", "Description").row("`a.b`", "`Number`", "one") .row("`a.b`", "`Number`", "one")
.row("`a.c`", "`String`", "two") .row("`a.c`", "`String`", "two")
.row("`a`", "`Object`", "three")); .row("`a`", "`Object`", "three"));
} }
@Test @RenderedSnippetTest
public void prefixedAdditionalDescriptors() throws IOException { void prefixedAdditionalDescriptors(OperationBuilder operationBuilder, AssertableSnippets snippets)
throws IOException {
PayloadDocumentation.requestPartFields("one", fieldWithPath("a").description("one")) PayloadDocumentation.requestPartFields("one", fieldWithPath("a").description("one"))
.andWithPrefix("a.", fieldWithPath("b").description("two"), fieldWithPath("c").description("three")) .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()) .part("one", "{\"a\": {\"b\": 5, \"c\": \"charlie\"}}".getBytes())
.build()); .build());
assertThat(this.generatedSnippets.requestPartFields("one")) assertThat(snippets.requestPartFields("one")).isTable((table) -> table.withHeader("Path", "Type", "Description")
.is(tableWithHeader("Path", "Type", "Description").row("`a`", "`Object`", "one") .row("`a`", "`Object`", "one")
.row("`a.b`", "`Number`", "two") .row("`a.b`", "`Number`", "two")
.row("`a.c`", "`String`", "three")); .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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with 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 java.io.IOException;
import org.junit.Test;
import org.springframework.http.HttpHeaders; import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.restdocs.AbstractSnippetTests; import org.springframework.restdocs.testfixtures.jupiter.AssertableSnippets;
import org.springframework.restdocs.templates.TemplateEngine; import org.springframework.restdocs.testfixtures.jupiter.OperationBuilder;
import org.springframework.restdocs.templates.TemplateFormat; import org.springframework.restdocs.testfixtures.jupiter.RenderedSnippetTest;
import org.springframework.restdocs.templates.TemplateResourceResolver; import org.springframework.restdocs.testfixtures.jupiter.SnippetTemplate;
import org.springframework.restdocs.templates.mustache.MustacheTemplateEngine;
import static org.assertj.core.api.Assertions.assertThat; 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.beneathPath;
import static org.springframework.restdocs.payload.PayloadDocumentation.responseBody; import static org.springframework.restdocs.payload.PayloadDocumentation.responseBody;
import static org.springframework.restdocs.snippet.Attributes.attributes; import static org.springframework.restdocs.snippet.Attributes.attributes;
@@ -41,77 +36,72 @@ import static org.springframework.restdocs.snippet.Attributes.key;
* *
* @author Andy Wilkinson * @author Andy Wilkinson
*/ */
public class ResponseBodySnippetTests extends AbstractSnippetTests { class ResponseBodySnippetTests {
public ResponseBodySnippetTests(String name, TemplateFormat templateFormat) { @RenderedSnippetTest
super(name, templateFormat); 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 @RenderedSnippetTest
public void responseWithBody() throws IOException { void responseWithNoBody(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
new ResponseBodySnippet().document(this.operationBuilder.response().content("some content").build()); new ResponseBodySnippet().document(operationBuilder.response().build());
assertThat(this.generatedSnippets.snippet("response-body")) assertThat(snippets.responseBody()).isCodeBlock((codeBlock) -> codeBlock.withOptions("nowrap").content(""));
.is(codeBlock(null, "nowrap").withContent("some content"));
} }
@Test @RenderedSnippetTest
public void responseWithNoBody() throws IOException { void responseWithJsonMediaType(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
new ResponseBodySnippet().document(this.operationBuilder.response().build()); new ResponseBodySnippet().document(
assertThat(this.generatedSnippets.snippet("response-body")).is(codeBlock(null, "nowrap").withContent("")); operationBuilder.response().header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE).build());
assertThat(snippets.responseBody())
.isCodeBlock((codeBlock) -> codeBlock.withLanguageAndOptions("json", "nowrap").content(""));
} }
@Test @RenderedSnippetTest
public void responseWithJsonMediaType() throws IOException { void responseWithJsonSubtypeMediaType(OperationBuilder operationBuilder, AssertableSnippets snippets)
new ResponseBodySnippet().document(this.operationBuilder.response() throws IOException {
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) new ResponseBodySnippet().document(operationBuilder.response()
.build());
assertThat(this.generatedSnippets.snippet("response-body")).is(codeBlock("json", "nowrap").withContent(""));
}
@Test
public void responseWithJsonSubtypeMediaType() throws IOException {
new ResponseBodySnippet().document(this.operationBuilder.response()
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_PROBLEM_JSON_VALUE) .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_PROBLEM_JSON_VALUE)
.build()); .build());
assertThat(this.generatedSnippets.snippet("response-body")).is(codeBlock("json", "nowrap").withContent("")); assertThat(snippets.responseBody())
.isCodeBlock((codeBlock) -> codeBlock.withLanguageAndOptions("json", "nowrap").content(""));
} }
@Test @RenderedSnippetTest
public void responseWithXmlMediaType() throws IOException { void responseWithXmlMediaType(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
new ResponseBodySnippet().document(this.operationBuilder.response() new ResponseBodySnippet().document(
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_XML_VALUE) operationBuilder.response().header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_XML_VALUE).build());
.build()); assertThat(snippets.responseBody())
assertThat(this.generatedSnippets.snippet("response-body")).is(codeBlock("xml", "nowrap").withContent("")); .isCodeBlock((codeBlock) -> codeBlock.withLanguageAndOptions("xml", "nowrap").content(""));
} }
@Test @RenderedSnippetTest
public void responseWithXmlSubtypeMediaType() throws IOException { void responseWithXmlSubtypeMediaType(OperationBuilder operationBuilder, AssertableSnippets snippets)
new ResponseBodySnippet().document(this.operationBuilder.response() throws IOException {
new ResponseBodySnippet().document(operationBuilder.response()
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_ATOM_XML_VALUE) .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_ATOM_XML_VALUE)
.build()); .build());
assertThat(this.generatedSnippets.snippet("response-body")).is(codeBlock("xml", "nowrap").withContent("")); assertThat(snippets.responseBody())
.isCodeBlock((codeBlock) -> codeBlock.withLanguageAndOptions("xml", "nowrap").content(""));
} }
@Test @RenderedSnippetTest
public void subsectionOfResponseBody() throws IOException { void subsectionOfResponseBody(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
responseBody(beneathPath("a.b")) responseBody(beneathPath("a.b"))
.document(this.operationBuilder.response().content("{\"a\":{\"b\":{\"c\":5}}}").build()); .document(operationBuilder.response().content("{\"a\":{\"b\":{\"c\":5}}}").build());
assertThat(this.generatedSnippets.snippet("response-body-beneath-a.b")) assertThat(snippets.responseBody("beneath-a.b"))
.is(codeBlock(null, "nowrap").withContent("{\"c\":5}")); .isCodeBlock((codeBlock) -> codeBlock.withOptions("nowrap").content("{\"c\":5}"));
} }
@Test @RenderedSnippetTest
public void customSnippetAttributes() throws IOException { @SnippetTemplate(snippet = "response-body", template = "response-body-with-language")
TemplateResourceResolver resolver = mock(TemplateResourceResolver.class); void customSnippetAttributes(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
given(resolver.resolveTemplateResource("response-body")) new ResponseBodySnippet(attributes(key("language").value("json")))
.willReturn(snippetResource("response-body-with-language")); .document(operationBuilder.response().content("{\"a\":\"alpha\"}").build());
new ResponseBodySnippet(attributes(key("language").value("json"))).document( assertThat(snippets.responseBody()).isCodeBlock(
this.operationBuilder.attribute(TemplateEngine.class.getName(), new MustacheTemplateEngine(resolver)) (codeBlock) -> codeBlock.withLanguageAndOptions("json", "nowrap").content("{\"a\":\"alpha\"}"));
.response()
.content("{\"a\":\"alpha\"}")
.build());
assertThat(this.generatedSnippets.snippet("response-body"))
.is(codeBlock("json", "nowrap").withContent("{\"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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with 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.io.IOException;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections;
import org.junit.Test;
import org.springframework.http.HttpHeaders; import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.restdocs.AbstractSnippetTests; import org.springframework.restdocs.snippet.SnippetException;
import org.springframework.restdocs.templates.TemplateEngine; import org.springframework.restdocs.testfixtures.jupiter.AssertableSnippets;
import org.springframework.restdocs.templates.TemplateFormat; import org.springframework.restdocs.testfixtures.jupiter.OperationBuilder;
import org.springframework.restdocs.templates.TemplateFormats; import org.springframework.restdocs.testfixtures.jupiter.RenderedSnippetTest;
import org.springframework.restdocs.templates.TemplateResourceResolver; import org.springframework.restdocs.testfixtures.jupiter.SnippetTemplate;
import org.springframework.restdocs.templates.mustache.MustacheTemplateEngine; import org.springframework.restdocs.testfixtures.jupiter.SnippetTest;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.BDDMockito.given; import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.mockito.Mockito.mock;
import static org.springframework.restdocs.payload.PayloadDocumentation.beneathPath; import static org.springframework.restdocs.payload.PayloadDocumentation.beneathPath;
import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath; import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath;
import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields; import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields;
@@ -45,355 +43,486 @@ import static org.springframework.restdocs.snippet.Attributes.key;
* @author Andy Wilkinson * @author Andy Wilkinson
* @author Sungjun Lee * @author Sungjun Lee
*/ */
public class ResponseFieldsSnippetTests extends AbstractSnippetTests { public class ResponseFieldsSnippetTests {
public ResponseFieldsSnippetTests(String name, TemplateFormat templateFormat) { @RenderedSnippetTest
super(name, templateFormat); void mapResponseWithFields(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
}
@Test
public void mapResponseWithFields() throws IOException {
new ResponseFieldsSnippet(Arrays.asList(fieldWithPath("id").description("one"), new ResponseFieldsSnippet(Arrays.asList(fieldWithPath("id").description("one"),
fieldWithPath("date").description("two"), fieldWithPath("assets").description("three"), fieldWithPath("date").description("two"), fieldWithPath("assets").description("three"),
fieldWithPath("assets[]").description("four"), fieldWithPath("assets[].id").description("five"), fieldWithPath("assets[]").description("four"), fieldWithPath("assets[].id").description("five"),
fieldWithPath("assets[].name").description("six"))) fieldWithPath("assets[].name").description("six")))
.document(this.operationBuilder.response() .document(operationBuilder.response()
.content("{\"id\": 67,\"date\": \"2015-01-20\",\"assets\":" + " [{\"id\":356,\"name\": \"sample\"}]}") .content("{\"id\": 67,\"date\": \"2015-01-20\",\"assets\":" + " [{\"id\":356,\"name\": \"sample\"}]}")
.build()); .build());
assertThat(this.generatedSnippets.responseFields()) assertThat(snippets.responseFields()).isTable((table) -> table.withHeader("Path", "Type", "Description")
.is(tableWithHeader("Path", "Type", "Description").row("`id`", "`Number`", "one") .row("`id`", "`Number`", "one")
.row("`date`", "`String`", "two") .row("`date`", "`String`", "two")
.row("`assets`", "`Array`", "three") .row("`assets`", "`Array`", "three")
.row("`assets[]`", "`Array`", "four") .row("`assets[]`", "`Array`", "four")
.row("`assets[].id`", "`Number`", "five") .row("`assets[].id`", "`Number`", "five")
.row("`assets[].name`", "`String`", "six")); .row("`assets[].name`", "`String`", "six"));
} }
@Test @RenderedSnippetTest
public void mapResponseWithNullField() throws IOException { void mapResponseWithNullField(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
new ResponseFieldsSnippet(Arrays.asList(fieldWithPath("a.b").description("one"))) new ResponseFieldsSnippet(Arrays.asList(fieldWithPath("a.b").description("one")))
.document(this.operationBuilder.response().content("{\"a\": {\"b\": null}}").build()); .document(operationBuilder.response().content("{\"a\": {\"b\": null}}").build());
assertThat(this.generatedSnippets.responseFields()) assertThat(snippets.responseFields())
.is(tableWithHeader("Path", "Type", "Description").row("`a.b`", "`Null`", "one")); .isTable((table) -> table.withHeader("Path", "Type", "Description").row("`a.b`", "`Null`", "one"));
} }
@Test @RenderedSnippetTest
public void subsectionOfMapResponse() throws IOException { void subsectionOfMapResponse(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
responseFields(beneathPath("a"), fieldWithPath("b").description("one"), fieldWithPath("c").description("two")) responseFields(beneathPath("a"), fieldWithPath("b").description("one"), fieldWithPath("c").description("two"))
.document(this.operationBuilder.response().content("{\"a\": {\"b\": 5, \"c\": \"charlie\"}}").build()); .document(operationBuilder.response().content("{\"a\": {\"b\": 5, \"c\": \"charlie\"}}").build());
assertThat(this.generatedSnippets.snippet("response-fields-beneath-a")) assertThat(snippets.responseFields("beneath-a"))
.is(tableWithHeader("Path", "Type", "Description").row("`b`", "`Number`", "one") .isTable((table) -> table.withHeader("Path", "Type", "Description")
.row("`b`", "`Number`", "one")
.row("`c`", "`String`", "two")); .row("`c`", "`String`", "two"));
} }
@Test @RenderedSnippetTest
public void subsectionOfMapResponseBeneathAnArray() throws IOException { void subsectionOfMapResponseBeneathAnArray(OperationBuilder operationBuilder, AssertableSnippets snippets)
throws IOException {
responseFields(beneathPath("a.b.[]"), fieldWithPath("c").description("one"), responseFields(beneathPath("a.b.[]"), fieldWithPath("c").description("one"),
fieldWithPath("d.[].e").description("two")) 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}]}]}}") .content("{\"a\": {\"b\": [{\"c\": 1, \"d\": [{\"e\": 5}]}, {\"c\": 3, \"d\": [{\"e\": 4}]}]}}")
.build()); .build());
assertThat(this.generatedSnippets.snippet("response-fields-beneath-a.b.[]")) assertThat(snippets.responseFields("beneath-a.b.[]"))
.is(tableWithHeader("Path", "Type", "Description").row("`c`", "`Number`", "one") .isTable((table) -> table.withHeader("Path", "Type", "Description")
.row("`c`", "`Number`", "one")
.row("`d.[].e`", "`Number`", "two")); .row("`d.[].e`", "`Number`", "two"));
} }
@Test @RenderedSnippetTest
public void subsectionOfMapResponseWithCommonsPrefix() throws IOException { void subsectionOfMapResponseWithCommonsPrefix(OperationBuilder operationBuilder, AssertableSnippets snippets)
throws IOException {
responseFields(beneathPath("a")).andWithPrefix("b.", fieldWithPath("c").description("two")) responseFields(beneathPath("a")).andWithPrefix("b.", fieldWithPath("c").description("two"))
.document(this.operationBuilder.response().content("{\"a\": {\"b\": {\"c\": \"charlie\"}}}").build()); .document(operationBuilder.response().content("{\"a\": {\"b\": {\"c\": \"charlie\"}}}").build());
assertThat(this.generatedSnippets.snippet("response-fields-beneath-a")) assertThat(snippets.responseFields("beneath-a"))
.is(tableWithHeader("Path", "Type", "Description").row("`b.c`", "`String`", "two")); .isTable((table) -> table.withHeader("Path", "Type", "Description").row("`b.c`", "`String`", "two"));
} }
@Test @RenderedSnippetTest
public void arrayResponseWithFields() throws IOException { void arrayResponseWithFields(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
new ResponseFieldsSnippet(Arrays.asList(fieldWithPath("[]a.b").description("one"), new ResponseFieldsSnippet(Arrays.asList(fieldWithPath("[]a.b").description("one"),
fieldWithPath("[]a.c").description("two"), fieldWithPath("[]a").description("three"))) 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\"}}]") .content("[{\"a\": {\"b\": 5, \"c\":\"charlie\"}}," + "{\"a\": {\"b\": 4, \"c\":\"chalk\"}}]")
.build()); .build());
assertThat(this.generatedSnippets.responseFields()) assertThat(snippets.responseFields()).isTable((table) -> table.withHeader("Path", "Type", "Description")
.is(tableWithHeader("Path", "Type", "Description").row("`[]a.b`", "`Number`", "one") .row("`[]a.b`", "`Number`", "one")
.row("`[]a.c`", "`String`", "two") .row("`[]a.c`", "`String`", "two")
.row("`[]a`", "`Object`", "three")); .row("`[]a`", "`Object`", "three"));
} }
@Test @RenderedSnippetTest
public void arrayResponseWithAlwaysNullField() throws IOException { void arrayResponseWithAlwaysNullField(OperationBuilder operationBuilder, AssertableSnippets snippets)
new ResponseFieldsSnippet(Arrays.asList(fieldWithPath("[]a.b").description("one"))) throws IOException {
.document(this.operationBuilder.response() new ResponseFieldsSnippet(Arrays.asList(fieldWithPath("[]a.b").description("one"))).document(
.content("[{\"a\": {\"b\": null}}," + "{\"a\": {\"b\": null}}]") operationBuilder.response().content("[{\"a\": {\"b\": null}}," + "{\"a\": {\"b\": null}}]").build());
.build()); assertThat(snippets.responseFields())
assertThat(this.generatedSnippets.responseFields()) .isTable((table) -> table.withHeader("Path", "Type", "Description").row("`[]a.b`", "`Null`", "one"));
.is(tableWithHeader("Path", "Type", "Description").row("`[]a.b`", "`Null`", "one"));
} }
@Test @RenderedSnippetTest
public void arrayResponse() throws IOException { void arrayResponse(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
new ResponseFieldsSnippet(Arrays.asList(fieldWithPath("[]").description("one"))) new ResponseFieldsSnippet(Arrays.asList(fieldWithPath("[]").description("one")))
.document(this.operationBuilder.response().content("[\"a\", \"b\", \"c\"]").build()); .document(operationBuilder.response().content("[\"a\", \"b\", \"c\"]").build());
assertThat(this.generatedSnippets.responseFields()) assertThat(snippets.responseFields())
.is(tableWithHeader("Path", "Type", "Description").row("`[]`", "`Array`", "one")); .isTable((table) -> table.withHeader("Path", "Type", "Description").row("`[]`", "`Array`", "one"));
} }
@Test @RenderedSnippetTest
public void ignoredResponseField() throws IOException { void ignoredResponseField(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
new ResponseFieldsSnippet( new ResponseFieldsSnippet(
Arrays.asList(fieldWithPath("a").ignored(), fieldWithPath("b").description("Field b"))) Arrays.asList(fieldWithPath("a").ignored(), fieldWithPath("b").description("Field b")))
.document(this.operationBuilder.response().content("{\"a\": 5, \"b\": 4}").build()); .document(operationBuilder.response().content("{\"a\": 5, \"b\": 4}").build());
assertThat(this.generatedSnippets.responseFields()) assertThat(snippets.responseFields())
.is(tableWithHeader("Path", "Type", "Description").row("`b`", "`Number`", "Field b")); .isTable((table) -> table.withHeader("Path", "Type", "Description").row("`b`", "`Number`", "Field b"));
} }
@Test @RenderedSnippetTest
public void allUndocumentedFieldsCanBeIgnored() throws IOException { void allUndocumentedFieldsCanBeIgnored(OperationBuilder operationBuilder, AssertableSnippets snippets)
throws IOException {
new ResponseFieldsSnippet(Arrays.asList(fieldWithPath("b").description("Field b")), true) new ResponseFieldsSnippet(Arrays.asList(fieldWithPath("b").description("Field b")), true)
.document(this.operationBuilder.response().content("{\"a\": 5, \"b\": 4}").build()); .document(operationBuilder.response().content("{\"a\": 5, \"b\": 4}").build());
assertThat(this.generatedSnippets.responseFields()) assertThat(snippets.responseFields())
.is(tableWithHeader("Path", "Type", "Description").row("`b`", "`Number`", "Field b")); .isTable((table) -> table.withHeader("Path", "Type", "Description").row("`b`", "`Number`", "Field b"));
} }
@Test @RenderedSnippetTest
public void allUndocumentedFieldsContinueToBeIgnoredAfterAddingDescriptors() throws IOException { void allUndocumentedFieldsContinueToBeIgnoredAfterAddingDescriptors(OperationBuilder operationBuilder,
AssertableSnippets snippets) throws IOException {
new ResponseFieldsSnippet(Arrays.asList(fieldWithPath("b").description("Field b")), true) new ResponseFieldsSnippet(Arrays.asList(fieldWithPath("b").description("Field b")), true)
.andWithPrefix("c.", fieldWithPath("d").description("Field d")) .andWithPrefix("c.", fieldWithPath("d").description("Field d"))
.document(this.operationBuilder.response().content("{\"a\":5,\"b\":4,\"c\":{\"d\": 3}}").build()); .document(operationBuilder.response().content("{\"a\":5,\"b\":4,\"c\":{\"d\": 3}}").build());
assertThat(this.generatedSnippets.responseFields()) assertThat(snippets.responseFields()).isTable((table) -> table.withHeader("Path", "Type", "Description")
.is(tableWithHeader("Path", "Type", "Description").row("`b`", "`Number`", "Field b") .row("`b`", "`Number`", "Field b")
.row("`c.d`", "`Number`", "Field d")); .row("`c.d`", "`Number`", "Field d"));
} }
@Test @RenderedSnippetTest
public void responseFieldsWithCustomAttributes() throws IOException { @SnippetTemplate(snippet = "response-fields", template = "response-fields-with-title")
TemplateResourceResolver resolver = mock(TemplateResourceResolver.class); void responseFieldsWithCustomAttributes(OperationBuilder operationBuilder, AssertableSnippets snippets)
given(resolver.resolveTemplateResource("response-fields")) throws IOException {
.willReturn(snippetResource("response-fields-with-title"));
new ResponseFieldsSnippet(Arrays.asList(fieldWithPath("a").description("one")), new ResponseFieldsSnippet(Arrays.asList(fieldWithPath("a").description("one")),
attributes(key("title").value("Custom title"))) attributes(key("title").value("Custom title")))
.document(this.operationBuilder .document(operationBuilder.response().content("{\"a\": \"foo\"}").build());
.attribute(TemplateEngine.class.getName(), new MustacheTemplateEngine(resolver)) assertThat(snippets.responseFields()).contains("Custom title");
.response()
.content("{\"a\": \"foo\"}")
.build());
assertThat(this.generatedSnippets.responseFields()).contains("Custom title");
} }
@Test @RenderedSnippetTest
public void missingOptionalResponseField() throws IOException { void missingOptionalResponseField(OperationBuilder operationBuilder, AssertableSnippets snippets)
throws IOException {
new ResponseFieldsSnippet( new ResponseFieldsSnippet(
Arrays.asList(fieldWithPath("a.b").description("one").type(JsonFieldType.STRING).optional())) Arrays.asList(fieldWithPath("a.b").description("one").type(JsonFieldType.STRING).optional()))
.document(this.operationBuilder.response().content("{}").build()); .document(operationBuilder.response().content("{}").build());
assertThat(this.generatedSnippets.responseFields()) assertThat(snippets.responseFields())
.is(tableWithHeader("Path", "Type", "Description").row("`a.b`", "`String`", "one")); .isTable((table) -> table.withHeader("Path", "Type", "Description").row("`a.b`", "`String`", "one"));
} }
@Test @RenderedSnippetTest
public void missingIgnoredOptionalResponseFieldDoesNotRequireAType() throws IOException { void missingIgnoredOptionalResponseFieldDoesNotRequireAType(OperationBuilder operationBuilder,
AssertableSnippets snippets) throws IOException {
new ResponseFieldsSnippet(Arrays.asList(fieldWithPath("a.b").description("one").ignored().optional())) new ResponseFieldsSnippet(Arrays.asList(fieldWithPath("a.b").description("one").ignored().optional()))
.document(this.operationBuilder.response().content("{}").build()); .document(operationBuilder.response().content("{}").build());
assertThat(this.generatedSnippets.responseFields()).is(tableWithHeader("Path", "Type", "Description")); assertThat(snippets.responseFields()).isTable((table) -> table.withHeader("Path", "Type", "Description"));
} }
@Test @RenderedSnippetTest
public void presentOptionalResponseField() throws IOException { void presentOptionalResponseField(OperationBuilder operationBuilder, AssertableSnippets snippets)
throws IOException {
new ResponseFieldsSnippet( new ResponseFieldsSnippet(
Arrays.asList(fieldWithPath("a.b").description("one").type(JsonFieldType.STRING).optional())) Arrays.asList(fieldWithPath("a.b").description("one").type(JsonFieldType.STRING).optional()))
.document(this.operationBuilder.response().content("{\"a\": { \"b\": \"bravo\"}}").build()); .document(operationBuilder.response().content("{\"a\": { \"b\": \"bravo\"}}").build());
assertThat(this.generatedSnippets.responseFields()) assertThat(snippets.responseFields())
.is(tableWithHeader("Path", "Type", "Description").row("`a.b`", "`String`", "one")); .isTable((table) -> table.withHeader("Path", "Type", "Description").row("`a.b`", "`String`", "one"));
} }
@Test @RenderedSnippetTest
public void responseFieldsWithCustomDescriptorAttributes() throws IOException { @SnippetTemplate(snippet = "response-fields", template = "response-fields-with-extra-column")
TemplateResourceResolver resolver = mock(TemplateResourceResolver.class); void responseFieldsWithCustomDescriptorAttributes(OperationBuilder operationBuilder, AssertableSnippets snippets)
given(resolver.resolveTemplateResource("response-fields")) throws IOException {
.willReturn(snippetResource("response-fields-with-extra-column"));
new ResponseFieldsSnippet( new ResponseFieldsSnippet(
Arrays.asList(fieldWithPath("a.b").description("one").attributes(key("foo").value("alpha")), Arrays.asList(fieldWithPath("a.b").description("one").attributes(key("foo").value("alpha")),
fieldWithPath("a.c").description("two").attributes(key("foo").value("bravo")), fieldWithPath("a.c").description("two").attributes(key("foo").value("bravo")),
fieldWithPath("a").description("three").attributes(key("foo").value("charlie")))) fieldWithPath("a").description("three").attributes(key("foo").value("charlie"))))
.document(this.operationBuilder .document(operationBuilder.response().content("{\"a\": {\"b\": 5, \"c\": \"charlie\"}}").build());
.attribute(TemplateEngine.class.getName(), new MustacheTemplateEngine(resolver)) assertThat(snippets.responseFields()).isTable((table) -> table.withHeader("Path", "Type", "Description", "Foo")
.response() .row("a.b", "Number", "one", "alpha")
.content("{\"a\": {\"b\": 5, \"c\": \"charlie\"}}") .row("a.c", "String", "two", "bravo")
.build()); .row("a", "Object", "three", "charlie"));
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"));
} }
@Test @RenderedSnippetTest
public void fieldWithExplicitExactlyMatchingType() throws IOException { void fieldWithExplicitExactlyMatchingType(OperationBuilder operationBuilder, AssertableSnippets snippets)
throws IOException {
new ResponseFieldsSnippet(Arrays.asList(fieldWithPath("a").description("one").type(JsonFieldType.NUMBER))) new ResponseFieldsSnippet(Arrays.asList(fieldWithPath("a").description("one").type(JsonFieldType.NUMBER)))
.document(this.operationBuilder.response().content("{\"a\": 5 }").build()); .document(operationBuilder.response().content("{\"a\": 5 }").build());
assertThat(this.generatedSnippets.responseFields()) assertThat(snippets.responseFields())
.is(tableWithHeader("Path", "Type", "Description").row("`a`", "`Number`", "one")); .isTable((table) -> table.withHeader("Path", "Type", "Description").row("`a`", "`Number`", "one"));
} }
@Test @RenderedSnippetTest
public void fieldWithExplicitVariesType() throws IOException { void fieldWithExplicitVariesType(OperationBuilder operationBuilder, AssertableSnippets snippets)
throws IOException {
new ResponseFieldsSnippet(Arrays.asList(fieldWithPath("a").description("one").type(JsonFieldType.VARIES))) new ResponseFieldsSnippet(Arrays.asList(fieldWithPath("a").description("one").type(JsonFieldType.VARIES)))
.document(this.operationBuilder.response().content("{\"a\": 5 }").build()); .document(operationBuilder.response().content("{\"a\": 5 }").build());
assertThat(this.generatedSnippets.responseFields()) assertThat(snippets.responseFields())
.is(tableWithHeader("Path", "Type", "Description").row("`a`", "`Varies`", "one")); .isTable((table) -> table.withHeader("Path", "Type", "Description").row("`a`", "`Varies`", "one"));
} }
@Test @RenderedSnippetTest
public void applicationXmlResponseFields() throws IOException { void applicationXmlResponseFields(OperationBuilder operationBuilder, AssertableSnippets snippets)
xmlResponseFields(MediaType.APPLICATION_XML); throws IOException {
xmlResponseFields(MediaType.APPLICATION_XML, operationBuilder, snippets);
} }
@Test @RenderedSnippetTest
public void textXmlResponseFields() throws IOException { void textXmlResponseFields(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
xmlResponseFields(MediaType.TEXT_XML); xmlResponseFields(MediaType.TEXT_XML, operationBuilder, snippets);
} }
@Test @RenderedSnippetTest
public void customXmlResponseFields() throws IOException { void customXmlResponseFields(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
xmlResponseFields(MediaType.parseMediaType("application/vnd.com.example+xml")); 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"), new ResponseFieldsSnippet(Arrays.asList(fieldWithPath("a/b").description("one").type("b"),
fieldWithPath("a/c").description("two").type("c"), fieldWithPath("a").description("three").type("a"))) 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>") .content("<a><b>5</b><c>charlie</c></a>")
.header(HttpHeaders.CONTENT_TYPE, contentType.toString()) .header(HttpHeaders.CONTENT_TYPE, contentType.toString())
.build()); .build());
assertThat(this.generatedSnippets.responseFields()) assertThat(snippets.responseFields()).isTable((table) -> table.withHeader("Path", "Type", "Description")
.is(tableWithHeader("Path", "Type", "Description").row("`a/b`", "`b`", "one") .row("`a/b`", "`b`", "one")
.row("`a/c`", "`c`", "two") .row("`a/c`", "`c`", "two")
.row("`a`", "`a`", "three")); .row("`a`", "`a`", "three"));
} }
@Test @RenderedSnippetTest
public void xmlAttribute() throws IOException { void xmlAttribute(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
new ResponseFieldsSnippet(Arrays.asList(fieldWithPath("a").description("one").type("b"), new ResponseFieldsSnippet(Arrays.asList(fieldWithPath("a").description("one").type("b"),
fieldWithPath("a/@id").description("two").type("c"))) fieldWithPath("a/@id").description("two").type("c")))
.document(this.operationBuilder.response() .document(operationBuilder.response()
.content("<a id=\"1\">foo</a>") .content("<a id=\"1\">foo</a>")
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_XML_VALUE) .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_XML_VALUE)
.build()); .build());
assertThat(this.generatedSnippets.responseFields()) assertThat(snippets.responseFields()).isTable((table) -> table.withHeader("Path", "Type", "Description")
.is(tableWithHeader("Path", "Type", "Description").row("`a`", "`b`", "one").row("`a/@id`", "`c`", "two")); .row("`a`", "`b`", "one")
.row("`a/@id`", "`c`", "two"));
} }
@Test @RenderedSnippetTest
public void missingOptionalXmlAttribute() throws IOException { void missingOptionalXmlAttribute(OperationBuilder operationBuilder, AssertableSnippets snippets)
throws IOException {
new ResponseFieldsSnippet(Arrays.asList(fieldWithPath("a").description("one").type("b"), new ResponseFieldsSnippet(Arrays.asList(fieldWithPath("a").description("one").type("b"),
fieldWithPath("a/@id").description("two").type("c").optional())) fieldWithPath("a/@id").description("two").type("c").optional()))
.document(this.operationBuilder.response() .document(operationBuilder.response()
.content("<a>foo</a>") .content("<a>foo</a>")
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_XML_VALUE) .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_XML_VALUE)
.build()); .build());
assertThat(this.generatedSnippets.responseFields()) assertThat(snippets.responseFields()).isTable((table) -> table.withHeader("Path", "Type", "Description")
.is(tableWithHeader("Path", "Type", "Description").row("`a`", "`b`", "one").row("`a/@id`", "`c`", "two")); .row("`a`", "`b`", "one")
.row("`a/@id`", "`c`", "two"));
} }
@Test @RenderedSnippetTest
public void undocumentedAttributeDoesNotCauseFailure() throws IOException { void undocumentedAttributeDoesNotCauseFailure(OperationBuilder operationBuilder, AssertableSnippets snippets)
throws IOException {
new ResponseFieldsSnippet(Arrays.asList(fieldWithPath("a").description("one").type("a"))) new ResponseFieldsSnippet(Arrays.asList(fieldWithPath("a").description("one").type("a")))
.document(this.operationBuilder.response() .document(operationBuilder.response()
.content("<a id=\"foo\">bar</a>") .content("<a id=\"foo\">bar</a>")
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_XML_VALUE) .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_XML_VALUE)
.build()); .build());
assertThat(this.generatedSnippets.responseFields()) assertThat(snippets.responseFields())
.is(tableWithHeader("Path", "Type", "Description").row("`a`", "`a`", "one")); .isTable((table) -> table.withHeader("Path", "Type", "Description").row("`a`", "`a`", "one"));
} }
@Test @RenderedSnippetTest
public void additionalDescriptors() throws IOException { void additionalDescriptors(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
PayloadDocumentation PayloadDocumentation
.responseFields(fieldWithPath("id").description("one"), fieldWithPath("date").description("two"), .responseFields(fieldWithPath("id").description("one"), fieldWithPath("date").description("two"),
fieldWithPath("assets").description("three")) fieldWithPath("assets").description("three"))
.and(fieldWithPath("assets[]").description("four"), fieldWithPath("assets[].id").description("five"), .and(fieldWithPath("assets[]").description("four"), fieldWithPath("assets[].id").description("five"),
fieldWithPath("assets[].name").description("six")) fieldWithPath("assets[].name").description("six"))
.document(this.operationBuilder.response() .document(operationBuilder.response()
.content("{\"id\": 67,\"date\": \"2015-01-20\",\"assets\":" + " [{\"id\":356,\"name\": \"sample\"}]}") .content("{\"id\": 67,\"date\": \"2015-01-20\",\"assets\":" + " [{\"id\":356,\"name\": \"sample\"}]}")
.build()); .build());
assertThat(this.generatedSnippets.responseFields()) assertThat(snippets.responseFields()).isTable((table) -> table.withHeader("Path", "Type", "Description")
.is(tableWithHeader("Path", "Type", "Description").row("`id`", "`Number`", "one") .row("`id`", "`Number`", "one")
.row("`date`", "`String`", "two") .row("`date`", "`String`", "two")
.row("`assets`", "`Array`", "three") .row("`assets`", "`Array`", "three")
.row("`assets[]`", "`Array`", "four") .row("`assets[]`", "`Array`", "four")
.row("`assets[].id`", "`Number`", "five") .row("`assets[].id`", "`Number`", "five")
.row("`assets[].name`", "`String`", "six")); .row("`assets[].name`", "`String`", "six"));
} }
@Test @RenderedSnippetTest
public void prefixedAdditionalDescriptors() throws IOException { void prefixedAdditionalDescriptors(OperationBuilder operationBuilder, AssertableSnippets snippets)
throws IOException {
PayloadDocumentation.responseFields(fieldWithPath("a").description("one")) PayloadDocumentation.responseFields(fieldWithPath("a").description("one"))
.andWithPrefix("a.", fieldWithPath("b").description("two"), fieldWithPath("c").description("three")) .andWithPrefix("a.", fieldWithPath("b").description("two"), fieldWithPath("c").description("three"))
.document(this.operationBuilder.response().content("{\"a\": {\"b\": 5, \"c\": \"charlie\"}}").build()); .document(operationBuilder.response().content("{\"a\": {\"b\": 5, \"c\": \"charlie\"}}").build());
assertThat(this.generatedSnippets.responseFields()) assertThat(snippets.responseFields()).isTable((table) -> table.withHeader("Path", "Type", "Description")
.is(tableWithHeader("Path", "Type", "Description").row("`a`", "`Object`", "one") .row("`a`", "`Object`", "one")
.row("`a.b`", "`Number`", "two") .row("`a.b`", "`Number`", "two")
.row("`a.c`", "`String`", "three")); .row("`a.c`", "`String`", "three"));
} }
@Test @RenderedSnippetTest
public void responseWithFieldsWithEscapedContent() throws IOException { void responseWithFieldsWithEscapedContent(OperationBuilder operationBuilder, AssertableSnippets snippets)
throws IOException {
new ResponseFieldsSnippet(Arrays.asList(fieldWithPath("Foo|Bar").type("one|two").description("three|four"))) new ResponseFieldsSnippet(Arrays.asList(fieldWithPath("Foo|Bar").type("one|two").description("three|four")))
.document(this.operationBuilder.response().content("{\"Foo|Bar\": 5}").build()); .document(operationBuilder.response().content("{\"Foo|Bar\": 5}").build());
assertThat(this.generatedSnippets.responseFields()).is(tableWithHeader("Path", "Type", "Description") assertThat(snippets.responseFields()).isTable(
.row(escapeIfNecessary("`Foo|Bar`"), escapeIfNecessary("`one|two`"), escapeIfNecessary("three|four"))); (table) -> table.withHeader("Path", "Type", "Description").row("`Foo|Bar`", "`one|two`", "three|four"));
} }
@Test @RenderedSnippetTest
public void mapResponseWithVaryingKeysMatchedUsingWildcard() throws IOException { void mapResponseWithVaryingKeysMatchedUsingWildcard(OperationBuilder operationBuilder, AssertableSnippets snippets)
throws IOException {
new ResponseFieldsSnippet(Arrays.asList(fieldWithPath("things.*.size").description("one"), new ResponseFieldsSnippet(Arrays.asList(fieldWithPath("things.*.size").description("one"),
fieldWithPath("things.*.type").description("two"))) fieldWithPath("things.*.type").description("two")))
.document(this.operationBuilder.response() .document(operationBuilder.response()
.content("{\"things\": {\"12abf\": {\"type\":" + "\"Whale\", \"size\": \"HUGE\"}," .content("{\"things\": {\"12abf\": {\"type\":" + "\"Whale\", \"size\": \"HUGE\"},"
+ "\"gzM33\" : {\"type\": \"Screw\"," + "\"size\": \"SMALL\"}}}") + "\"gzM33\" : {\"type\": \"Screw\"," + "\"size\": \"SMALL\"}}}")
.build()); .build());
assertThat(this.generatedSnippets.responseFields()) assertThat(snippets.responseFields()).isTable((table) -> table.withHeader("Path", "Type", "Description")
.is(tableWithHeader("Path", "Type", "Description").row("`things.*.size`", "`String`", "one") .row("`things.*.size`", "`String`", "one")
.row("`things.*.type`", "`String`", "two")); .row("`things.*.type`", "`String`", "two"));
} }
@Test @RenderedSnippetTest
public void responseWithArrayContainingFieldThatIsSometimesNull() throws IOException { void responseWithArrayContainingFieldThatIsSometimesNull(OperationBuilder operationBuilder,
AssertableSnippets snippets) throws IOException {
new ResponseFieldsSnippet( new ResponseFieldsSnippet(
Arrays.asList(fieldWithPath("assets[].name").description("one").type(JsonFieldType.STRING).optional())) Arrays.asList(fieldWithPath("assets[].name").description("one").type(JsonFieldType.STRING).optional()))
.document(this.operationBuilder.response() .document(operationBuilder.response()
.content("{\"assets\": [" + "{\"name\": \"sample1\"}, " + "{\"name\": null}, " .content("{\"assets\": [" + "{\"name\": \"sample1\"}, " + "{\"name\": null}, "
+ "{\"name\": \"sample2\"}]}") + "{\"name\": \"sample2\"}]}")
.build()); .build());
assertThat(this.generatedSnippets.responseFields()) assertThat(snippets.responseFields()).isTable(
.is(tableWithHeader("Path", "Type", "Description").row("`assets[].name`", "`String`", "one")); (table) -> table.withHeader("Path", "Type", "Description").row("`assets[].name`", "`String`", "one"));
} }
@Test @RenderedSnippetTest
public void optionalFieldBeneathArrayThatIsSometimesAbsent() throws IOException { void optionalFieldBeneathArrayThatIsSometimesAbsent(OperationBuilder operationBuilder, AssertableSnippets snippets)
throws IOException {
new ResponseFieldsSnippet( new ResponseFieldsSnippet(
Arrays.asList(fieldWithPath("a[].b").description("one").type(JsonFieldType.NUMBER).optional(), Arrays.asList(fieldWithPath("a[].b").description("one").type(JsonFieldType.NUMBER).optional(),
fieldWithPath("a[].c").description("two").type(JsonFieldType.NUMBER))) 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}]}") .content("{\"a\":[{\"b\": 1,\"c\": 2}, " + "{\"c\": 2}, {\"b\": 1,\"c\": 2}]}")
.build()); .build());
assertThat(this.generatedSnippets.responseFields()) assertThat(snippets.responseFields()).isTable((table) -> table.withHeader("Path", "Type", "Description")
.is(tableWithHeader("Path", "Type", "Description").row("`a[].b`", "`Number`", "one") .row("`a[].b`", "`Number`", "one")
.row("`a[].c`", "`Number`", "two")); .row("`a[].c`", "`Number`", "two"));
} }
@Test @RenderedSnippetTest
public void typeDeterminationDoesNotSetTypeOnDescriptor() throws IOException { void typeDeterminationDoesNotSetTypeOnDescriptor(OperationBuilder operationBuilder, AssertableSnippets snippets)
throws IOException {
FieldDescriptor descriptor = fieldWithPath("id").description("one"); FieldDescriptor descriptor = fieldWithPath("id").description("one");
new ResponseFieldsSnippet(Arrays.asList(descriptor)) 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(descriptor.getType()).isNull();
assertThat(this.generatedSnippets.responseFields()) assertThat(snippets.responseFields())
.is(tableWithHeader("Path", "Type", "Description").row("`id`", "`Number`", "one")); .isTable((table) -> table.withHeader("Path", "Type", "Description").row("`id`", "`Number`", "one"));
} }
private String escapeIfNecessary(String input) { @SnippetTest
if (this.templateFormat.getId().equals(TemplateFormats.markdown().getId())) { void attemptToDocumentFieldsWithNoResponseBody(OperationBuilder operationBuilder) {
return input; assertThatExceptionOfType(SnippetException.class)
} .isThrownBy(() -> new ResponseFieldsSnippet(Arrays.asList(fieldWithPath("a").description("one")))
return input.replace("|", "\\|"); .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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with 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.Collections;
import java.util.List; 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.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType; 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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with 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.io.IOException;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections;
import org.junit.Test; import org.springframework.restdocs.snippet.SnippetException;
import org.springframework.restdocs.testfixtures.jupiter.AssertableSnippets;
import org.springframework.restdocs.AbstractSnippetTests; import org.springframework.restdocs.testfixtures.jupiter.OperationBuilder;
import org.springframework.restdocs.templates.TemplateEngine; import org.springframework.restdocs.testfixtures.jupiter.RenderedSnippetTest;
import org.springframework.restdocs.templates.TemplateFormat; import org.springframework.restdocs.testfixtures.jupiter.SnippetTemplate;
import org.springframework.restdocs.templates.TemplateFormats; import org.springframework.restdocs.testfixtures.jupiter.SnippetTest;
import org.springframework.restdocs.templates.TemplateResourceResolver;
import org.springframework.restdocs.templates.mustache.MustacheTemplateEngine;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.BDDMockito.given; import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.mockito.Mockito.mock;
import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName; import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName;
import static org.springframework.restdocs.snippet.Attributes.attributes; import static org.springframework.restdocs.snippet.Attributes.attributes;
import static org.springframework.restdocs.snippet.Attributes.key; import static org.springframework.restdocs.snippet.Attributes.key;
@@ -40,147 +38,151 @@ import static org.springframework.restdocs.snippet.Attributes.key;
* *
* @author Andy Wilkinson * @author Andy Wilkinson
*/ */
public class FormParametersSnippetTests extends AbstractSnippetTests { class FormParametersSnippetTests {
public FormParametersSnippetTests(String name, TemplateFormat templateFormat) { @RenderedSnippetTest
super(name, templateFormat); void formParameters(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
}
@Test
public void formParameters() throws IOException {
new FormParametersSnippet( new FormParametersSnippet(
Arrays.asList(parameterWithName("a").description("one"), parameterWithName("b").description("two"))) Arrays.asList(parameterWithName("a").description("one"), parameterWithName("b").description("two")))
.document(this.operationBuilder.request("http://localhost").content("a=alpha&b=bravo").build()); .document(operationBuilder.request("http://localhost").content("a=alpha&b=bravo").build());
assertThat(this.generatedSnippets.formParameters()) assertThat(snippets.formParameters())
.is(tableWithHeader("Parameter", "Description").row("`a`", "one").row("`b`", "two")); .isTable((table) -> table.withHeader("Parameter", "Description").row("`a`", "one").row("`b`", "two"));
} }
@Test @RenderedSnippetTest
public void formParameterWithNoValue() throws IOException { void formParameterWithNoValue(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
new FormParametersSnippet(Arrays.asList(parameterWithName("a").description("one"))) new FormParametersSnippet(Arrays.asList(parameterWithName("a").description("one")))
.document(this.operationBuilder.request("http://localhost").content("a=").build()); .document(operationBuilder.request("http://localhost").content("a=").build());
assertThat(this.generatedSnippets.formParameters()) assertThat(snippets.formParameters())
.is(tableWithHeader("Parameter", "Description").row("`a`", "one")); .isTable((table) -> table.withHeader("Parameter", "Description").row("`a`", "one"));
} }
@Test @RenderedSnippetTest
public void ignoredFormParameter() throws IOException { void ignoredFormParameter(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
new FormParametersSnippet( new FormParametersSnippet(
Arrays.asList(parameterWithName("a").ignored(), parameterWithName("b").description("two"))) Arrays.asList(parameterWithName("a").ignored(), parameterWithName("b").description("two")))
.document(this.operationBuilder.request("http://localhost").content("a=alpha&b=bravo").build()); .document(operationBuilder.request("http://localhost").content("a=alpha&b=bravo").build());
assertThat(this.generatedSnippets.formParameters()) assertThat(snippets.formParameters())
.is(tableWithHeader("Parameter", "Description").row("`b`", "two")); .isTable((table) -> table.withHeader("Parameter", "Description").row("`b`", "two"));
} }
@Test @RenderedSnippetTest
public void allUndocumentedFormParametersCanBeIgnored() throws IOException { void allUndocumentedFormParametersCanBeIgnored(OperationBuilder operationBuilder, AssertableSnippets snippets)
throws IOException {
new FormParametersSnippet(Arrays.asList(parameterWithName("b").description("two")), true) new FormParametersSnippet(Arrays.asList(parameterWithName("b").description("two")), true)
.document(this.operationBuilder.request("http://localhost").content("a=alpha&b=bravo").build()); .document(operationBuilder.request("http://localhost").content("a=alpha&b=bravo").build());
assertThat(this.generatedSnippets.formParameters()) assertThat(snippets.formParameters())
.is(tableWithHeader("Parameter", "Description").row("`b`", "two")); .isTable((table) -> table.withHeader("Parameter", "Description").row("`b`", "two"));
} }
@Test @RenderedSnippetTest
public void missingOptionalFormParameter() throws IOException { void missingOptionalFormParameter(OperationBuilder operationBuilder, AssertableSnippets snippets)
throws IOException {
new FormParametersSnippet(Arrays.asList(parameterWithName("a").description("one").optional(), new FormParametersSnippet(Arrays.asList(parameterWithName("a").description("one").optional(),
parameterWithName("b").description("two"))) parameterWithName("b").description("two")))
.document(this.operationBuilder.request("http://localhost").content("b=bravo").build()); .document(operationBuilder.request("http://localhost").content("b=bravo").build());
assertThat(this.generatedSnippets.formParameters()) assertThat(snippets.formParameters())
.is(tableWithHeader("Parameter", "Description").row("`a`", "one").row("`b`", "two")); .isTable((table) -> table.withHeader("Parameter", "Description").row("`a`", "one").row("`b`", "two"));
} }
@Test @RenderedSnippetTest
public void presentOptionalFormParameter() throws IOException { void presentOptionalFormParameter(OperationBuilder operationBuilder, AssertableSnippets snippets)
throws IOException {
new FormParametersSnippet(Arrays.asList(parameterWithName("a").description("one").optional())) new FormParametersSnippet(Arrays.asList(parameterWithName("a").description("one").optional()))
.document(this.operationBuilder.request("http://localhost").content("a=alpha").build()); .document(operationBuilder.request("http://localhost").content("a=alpha").build());
assertThat(this.generatedSnippets.formParameters()) assertThat(snippets.formParameters())
.is(tableWithHeader("Parameter", "Description").row("`a`", "one")); .isTable((table) -> table.withHeader("Parameter", "Description").row("`a`", "one"));
} }
@Test @RenderedSnippetTest
public void formParametersWithCustomAttributes() throws IOException { @SnippetTemplate(snippet = "form-parameters", template = "form-parameters-with-title")
TemplateResourceResolver resolver = mock(TemplateResourceResolver.class); void formParametersWithCustomAttributes(OperationBuilder operationBuilder, AssertableSnippets snippets)
given(resolver.resolveTemplateResource("form-parameters")) throws IOException {
.willReturn(snippetResource("form-parameters-with-title"));
new FormParametersSnippet( new FormParametersSnippet(
Arrays.asList(parameterWithName("a").description("one").attributes(key("foo").value("alpha")), Arrays.asList(parameterWithName("a").description("one").attributes(key("foo").value("alpha")),
parameterWithName("b").description("two").attributes(key("foo").value("bravo"))), parameterWithName("b").description("two").attributes(key("foo").value("bravo"))),
attributes(key("title").value("The title"))) attributes(key("title").value("The title")))
.document(this.operationBuilder .document(operationBuilder.request("http://localhost").content("a=alpha&b=bravo").build());
.attribute(TemplateEngine.class.getName(), new MustacheTemplateEngine(resolver)) assertThat(snippets.formParameters()).contains("The title");
.request("http://localhost")
.content("a=alpha&b=bravo")
.build());
assertThat(this.generatedSnippets.formParameters()).contains("The title");
} }
@Test @RenderedSnippetTest
public void formParametersWithCustomDescriptorAttributes() throws IOException { @SnippetTemplate(snippet = "form-parameters", template = "form-parameters-with-extra-column")
TemplateResourceResolver resolver = mock(TemplateResourceResolver.class); void formParametersWithCustomDescriptorAttributes(OperationBuilder operationBuilder, AssertableSnippets snippets)
given(resolver.resolveTemplateResource("form-parameters")) throws IOException {
.willReturn(snippetResource("form-parameters-with-extra-column"));
new FormParametersSnippet( new FormParametersSnippet(
Arrays.asList(parameterWithName("a").description("one").attributes(key("foo").value("alpha")), Arrays.asList(parameterWithName("a").description("one").attributes(key("foo").value("alpha")),
parameterWithName("b").description("two").attributes(key("foo").value("bravo")))) parameterWithName("b").description("two").attributes(key("foo").value("bravo"))))
.document(this.operationBuilder .document(operationBuilder.request("http://localhost").content("a=alpha&b=bravo").build());
.attribute(TemplateEngine.class.getName(), new MustacheTemplateEngine(resolver)) assertThat(snippets.formParameters()).isTable((table) -> table.withHeader("Parameter", "Description", "Foo")
.request("http://localhost") .row("a", "one", "alpha")
.content("a=alpha&b=bravo") .row("b", "two", "bravo"));
.build());
assertThat(this.generatedSnippets.formParameters())
.is(tableWithHeader("Parameter", "Description", "Foo").row("a", "one", "alpha").row("b", "two", "bravo"));
} }
@Test @RenderedSnippetTest
public void formParametersWithOptionalColumn() throws IOException { @SnippetTemplate(snippet = "form-parameters", template = "form-parameters-with-optional-column")
TemplateResourceResolver resolver = mock(TemplateResourceResolver.class); void formParametersWithOptionalColumn(OperationBuilder operationBuilder, AssertableSnippets snippets)
given(resolver.resolveTemplateResource("form-parameters")) throws IOException {
.willReturn(snippetResource("form-parameters-with-optional-column"));
new FormParametersSnippet(Arrays.asList(parameterWithName("a").description("one").optional(), new FormParametersSnippet(Arrays.asList(parameterWithName("a").description("one").optional(),
parameterWithName("b").description("two"))) parameterWithName("b").description("two")))
.document(this.operationBuilder .document(operationBuilder.request("http://localhost").content("a=alpha&b=bravo").build());
.attribute(TemplateEngine.class.getName(), new MustacheTemplateEngine(resolver)) assertThat(snippets.formParameters())
.request("http://localhost") .isTable((table) -> table.withHeader("Parameter", "Optional", "Description")
.content("a=alpha&b=bravo") .row("a", "true", "one")
.build());
assertThat(this.generatedSnippets.formParameters())
.is(tableWithHeader("Parameter", "Optional", "Description").row("a", "true", "one")
.row("b", "false", "two")); .row("b", "false", "two"));
} }
@Test @RenderedSnippetTest
public void additionalDescriptors() throws IOException { void additionalDescriptors(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
RequestDocumentation.formParameters(parameterWithName("a").description("one")) RequestDocumentation.formParameters(parameterWithName("a").description("one"))
.and(parameterWithName("b").description("two")) .and(parameterWithName("b").description("two"))
.document(this.operationBuilder.request("http://localhost").content("a=alpha&b=bravo").build()); .document(operationBuilder.request("http://localhost").content("a=alpha&b=bravo").build());
assertThat(this.generatedSnippets.formParameters()) assertThat(snippets.formParameters())
.is(tableWithHeader("Parameter", "Description").row("`a`", "one").row("`b`", "two")); .isTable((table) -> table.withHeader("Parameter", "Description").row("`a`", "one").row("`b`", "two"));
} }
@Test @RenderedSnippetTest
public void additionalDescriptorsWithRelaxedFormParameters() throws IOException { void additionalDescriptorsWithRelaxedFormParameters(OperationBuilder operationBuilder, AssertableSnippets snippets)
throws IOException {
RequestDocumentation.relaxedFormParameters(parameterWithName("a").description("one")) RequestDocumentation.relaxedFormParameters(parameterWithName("a").description("one"))
.and(parameterWithName("b").description("two")) .and(parameterWithName("b").description("two"))
.document(this.operationBuilder.request("http://localhost") .document(operationBuilder.request("http://localhost").content("a=alpha&b=bravo&c=undocumented").build());
.content("a=alpha&b=bravo&c=undocumented") assertThat(snippets.formParameters())
.build()); .isTable((table) -> table.withHeader("Parameter", "Description").row("`a`", "one").row("`b`", "two"));
assertThat(this.generatedSnippets.formParameters())
.is(tableWithHeader("Parameter", "Description").row("`a`", "one").row("`b`", "two"));
} }
@Test @RenderedSnippetTest
public void formParametersWithEscapedContent() throws IOException { void formParametersWithEscapedContent(OperationBuilder operationBuilder, AssertableSnippets snippets)
throws IOException {
RequestDocumentation.formParameters(parameterWithName("Foo|Bar").description("one|two")) RequestDocumentation.formParameters(parameterWithName("Foo|Bar").description("one|two"))
.document(this.operationBuilder.request("http://localhost").content("Foo%7CBar=baz").build()); .document(operationBuilder.request("http://localhost").content("Foo%7CBar=baz").build());
assertThat(this.generatedSnippets.formParameters()).is(tableWithHeader("Parameter", "Description") assertThat(snippets.formParameters())
.row(escapeIfNecessary("`Foo|Bar`"), escapeIfNecessary("one|two"))); .isTable((table) -> table.withHeader("Parameter", "Description").row("`Foo|Bar`", "one|two"));
} }
private String escapeIfNecessary(String input) { @SnippetTest
if (this.templateFormat.getId().equals(TemplateFormats.markdown().getId())) { void undocumentedParameter(OperationBuilder operationBuilder) {
return input; assertThatExceptionOfType(SnippetException.class)
} .isThrownBy(() -> new FormParametersSnippet(Collections.<ParameterDescriptor>emptyList())
return input.replace("|", "\\|"); .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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with 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.io.IOException;
import java.util.Arrays; 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.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.TemplateFormat;
import org.springframework.restdocs.templates.TemplateFormats; import org.springframework.restdocs.templates.TemplateFormats;
import org.springframework.restdocs.templates.TemplateResourceResolver; import org.springframework.restdocs.testfixtures.jupiter.AssertableSnippets;
import org.springframework.restdocs.templates.mustache.MustacheTemplateEngine; 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.assertj.core.api.Assertions.assertThat;
import static org.mockito.BDDMockito.given; import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.mockito.Mockito.mock;
import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName; import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName;
import static org.springframework.restdocs.snippet.Attributes.attributes; import static org.springframework.restdocs.snippet.Attributes.attributes;
import static org.springframework.restdocs.snippet.Attributes.key; import static org.springframework.restdocs.snippet.Attributes.key;
@@ -41,164 +41,192 @@ import static org.springframework.restdocs.snippet.Attributes.key;
* *
* @author Andy Wilkinson * @author Andy Wilkinson
*/ */
public class PathParametersSnippetTests extends AbstractSnippetTests { class PathParametersSnippetTests {
public PathParametersSnippetTests(String name, TemplateFormat templateFormat) { @RenderedSnippetTest
super(name, templateFormat); void pathParameters(OperationBuilder operationBuilder, AssertableSnippets snippets, TemplateFormat templateFormat)
} throws IOException {
@Test
public void pathParameters() throws IOException {
new PathParametersSnippet( new PathParametersSnippet(
Arrays.asList(parameterWithName("a").description("one"), parameterWithName("b").description("two"))) Arrays.asList(parameterWithName("a").description("one"), parameterWithName("b").description("two")))
.document( .document(operationBuilder.attribute(RestDocumentationGenerator.ATTRIBUTE_NAME_URL_TEMPLATE, "/{a}/{b}")
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}")
.build()); .build());
assertThat(this.generatedSnippets.pathParameters()) assertThat(snippets.pathParameters())
.is(tableWithTitleAndHeader(getTitle("/{a}"), "Parameter", "Description").row("`a`", "one") .isTable((table) -> table.withTitleAndHeader(getTitle(templateFormat), "Parameter", "Description")
.row("`a`", "one")
.row("`b`", "two")); .row("`b`", "two"));
} }
@Test @RenderedSnippetTest
public void presentOptionalPathParameter() throws IOException { void ignoredPathParameter(OperationBuilder operationBuilder, AssertableSnippets snippets,
new PathParametersSnippet(Arrays.asList(parameterWithName("a").description("one").optional())) TemplateFormat templateFormat) throws IOException {
.document(this.operationBuilder.attribute(RestDocumentationGenerator.ATTRIBUTE_NAME_URL_TEMPLATE, "/{a}") new PathParametersSnippet(
Arrays.asList(parameterWithName("a").ignored(), parameterWithName("b").description("two")))
.document(operationBuilder.attribute(RestDocumentationGenerator.ATTRIBUTE_NAME_URL_TEMPLATE, "/{a}/{b}")
.build()); .build());
assertThat(this.generatedSnippets.pathParameters()) assertThat(snippets.pathParameters())
.is(tableWithTitleAndHeader(getTitle("/{a}"), "Parameter", "Description").row("`a`", "one")); .isTable((table) -> table.withTitleAndHeader(getTitle(templateFormat), "Parameter", "Description")
.row("`b`", "two"));
} }
@Test @RenderedSnippetTest
public void pathParametersWithQueryString() throws IOException { 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( new PathParametersSnippet(
Arrays.asList(parameterWithName("a").description("one"), parameterWithName("b").description("two"))) 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") .attribute(RestDocumentationGenerator.ATTRIBUTE_NAME_URL_TEMPLATE, "/{a}/{b}?foo=bar")
.build()); .build());
assertThat(this.generatedSnippets.pathParameters()) assertThat(snippets.pathParameters())
.is(tableWithTitleAndHeader(getTitle(), "Parameter", "Description").row("`a`", "one").row("`b`", "two")); .isTable((table) -> table.withTitleAndHeader(getTitle(templateFormat), "Parameter", "Description")
.row("`a`", "one")
.row("`b`", "two"));
} }
@Test @RenderedSnippetTest
public void pathParametersWithQueryStringWithParameters() throws IOException { void pathParametersWithQueryStringWithParameters(OperationBuilder operationBuilder, AssertableSnippets snippets,
TemplateFormat templateFormat) throws IOException {
new PathParametersSnippet( new PathParametersSnippet(
Arrays.asList(parameterWithName("a").description("one"), parameterWithName("b").description("two"))) 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}") .attribute(RestDocumentationGenerator.ATTRIBUTE_NAME_URL_TEMPLATE, "/{a}/{b}?foo={c}")
.build()); .build());
assertThat(this.generatedSnippets.pathParameters()) assertThat(snippets.pathParameters())
.is(tableWithTitleAndHeader(getTitle(), "Parameter", "Description").row("`a`", "one").row("`b`", "two")); .isTable((table) -> table.withTitleAndHeader(getTitle(templateFormat), "Parameter", "Description")
.row("`a`", "one")
.row("`b`", "two"));
} }
@Test @RenderedSnippetTest
public void pathParametersWithCustomAttributes() throws IOException { @SnippetTemplate(snippet = "path-parameters", template = "path-parameters-with-title")
TemplateResourceResolver resolver = mock(TemplateResourceResolver.class); void pathParametersWithCustomAttributes(OperationBuilder operationBuilder, AssertableSnippets snippets,
given(resolver.resolveTemplateResource("path-parameters")) TemplateFormat templateFormat) throws IOException {
.willReturn(snippetResource("path-parameters-with-title"));
new PathParametersSnippet( new PathParametersSnippet(
Arrays.asList(parameterWithName("a").description("one").attributes(key("foo").value("alpha")), Arrays.asList(parameterWithName("a").description("one").attributes(key("foo").value("alpha")),
parameterWithName("b").description("two").attributes(key("foo").value("bravo"))), parameterWithName("b").description("two").attributes(key("foo").value("bravo"))),
attributes(key("title").value("The title"))) attributes(key("title").value("The title")))
.document( .document(operationBuilder.attribute(RestDocumentationGenerator.ATTRIBUTE_NAME_URL_TEMPLATE, "/{a}/{b}")
this.operationBuilder.attribute(RestDocumentationGenerator.ATTRIBUTE_NAME_URL_TEMPLATE, "/{a}/{b}") .build());
.attribute(TemplateEngine.class.getName(), new MustacheTemplateEngine(resolver)) assertThat(snippets.pathParameters()).contains("The title");
.build());
assertThat(this.generatedSnippets.pathParameters()).contains("The title");
} }
@Test @RenderedSnippetTest
public void pathParametersWithCustomDescriptorAttributes() throws IOException { @SnippetTemplate(snippet = "path-parameters", template = "path-parameters-with-extra-column")
TemplateResourceResolver resolver = mock(TemplateResourceResolver.class); void pathParametersWithCustomDescriptorAttributes(OperationBuilder operationBuilder, AssertableSnippets snippets,
given(resolver.resolveTemplateResource("path-parameters")) TemplateFormat templateFormat) throws IOException {
.willReturn(snippetResource("path-parameters-with-extra-column"));
new PathParametersSnippet( new PathParametersSnippet(
Arrays.asList(parameterWithName("a").description("one").attributes(key("foo").value("alpha")), Arrays.asList(parameterWithName("a").description("one").attributes(key("foo").value("alpha")),
parameterWithName("b").description("two").attributes(key("foo").value("bravo")))) parameterWithName("b").description("two").attributes(key("foo").value("bravo"))))
.document( .document(operationBuilder.attribute(RestDocumentationGenerator.ATTRIBUTE_NAME_URL_TEMPLATE, "/{a}/{b}")
this.operationBuilder.attribute(RestDocumentationGenerator.ATTRIBUTE_NAME_URL_TEMPLATE, "/{a}/{b}") .build());
.attribute(TemplateEngine.class.getName(), new MustacheTemplateEngine(resolver)) assertThat(snippets.pathParameters()).isTable((table) -> table.withHeader("Parameter", "Description", "Foo")
.build()); .row("a", "one", "alpha")
assertThat(this.generatedSnippets.pathParameters()) .row("b", "two", "bravo"));
.is(tableWithHeader("Parameter", "Description", "Foo").row("a", "one", "alpha").row("b", "two", "bravo"));
} }
@Test @RenderedSnippetTest
public void additionalDescriptors() throws IOException { void additionalDescriptors(OperationBuilder operationBuilder, AssertableSnippets snippets,
TemplateFormat templateFormat) throws IOException {
RequestDocumentation.pathParameters(parameterWithName("a").description("one")) RequestDocumentation.pathParameters(parameterWithName("a").description("one"))
.and(parameterWithName("b").description("two")) .and(parameterWithName("b").description("two"))
.document( .document(operationBuilder.attribute(RestDocumentationGenerator.ATTRIBUTE_NAME_URL_TEMPLATE, "/{a}/{b}")
this.operationBuilder.attribute(RestDocumentationGenerator.ATTRIBUTE_NAME_URL_TEMPLATE, "/{a}/{b}") .build());
.build()); assertThat(snippets.pathParameters()).isTable(
assertThat(this.generatedSnippets.pathParameters()) (table) -> table.withTitleAndHeader(getTitle(templateFormat, "/{a}/{b}"), "Parameter", "Description")
.is(tableWithTitleAndHeader(getTitle(), "Parameter", "Description").row("`a`", "one").row("`b`", "two")); .row("`a`", "one")
.row("`b`", "two"));
} }
@Test @RenderedSnippetTest
public void additionalDescriptorsWithRelaxedRequestParameters() throws IOException { void additionalDescriptorsWithRelaxedRequestParameters(OperationBuilder operationBuilder,
AssertableSnippets snippets, TemplateFormat templateFormat) throws IOException {
RequestDocumentation.relaxedPathParameters(parameterWithName("a").description("one")) RequestDocumentation.relaxedPathParameters(parameterWithName("a").description("one"))
.and(parameterWithName("b").description("two")) .and(parameterWithName("b").description("two"))
.document(this.operationBuilder .document(operationBuilder.attribute(RestDocumentationGenerator.ATTRIBUTE_NAME_URL_TEMPLATE, "/{a}/{b}/{c}")
.attribute(RestDocumentationGenerator.ATTRIBUTE_NAME_URL_TEMPLATE, "/{a}/{b}/{c}")
.build()); .build());
assertThat(this.generatedSnippets.pathParameters()) assertThat(snippets.pathParameters()).isTable((table) -> table
.is(tableWithTitleAndHeader(getTitle("/{a}/{b}/{c}"), "Parameter", "Description").row("`a`", "one") .withTitleAndHeader(getTitle(templateFormat, "/{a}/{b}/{c}"), "Parameter", "Description")
.row("`b`", "two")); .row("`a`", "one")
.row("`b`", "two"));
} }
@Test @RenderedSnippetTest
public void pathParametersWithEscapedContent() throws IOException { void pathParametersWithEscapedContent(OperationBuilder operationBuilder, AssertableSnippets snippets,
TemplateFormat templateFormat) throws IOException {
RequestDocumentation.pathParameters(parameterWithName("Foo|Bar").description("one|two")) RequestDocumentation.pathParameters(parameterWithName("Foo|Bar").description("one|two"))
.document( .document(operationBuilder.attribute(RestDocumentationGenerator.ATTRIBUTE_NAME_URL_TEMPLATE, "{Foo|Bar}")
this.operationBuilder.attribute(RestDocumentationGenerator.ATTRIBUTE_NAME_URL_TEMPLATE, "{Foo|Bar}") .build());
.build()); assertThat(snippets.pathParameters()).isTable(
assertThat(this.generatedSnippets.pathParameters()) (table) -> table.withTitleAndHeader(getTitle(templateFormat, "{Foo|Bar}"), "Parameter", "Description")
.is(tableWithTitleAndHeader(getTitle("{Foo|Bar}"), "Parameter", "Description") .row("`Foo|Bar`", "one|two"));
.row(escapeIfNecessary("`Foo|Bar`"), escapeIfNecessary("one|two")));
} }
private String escapeIfNecessary(String input) { @SnippetTest
if (this.templateFormat.getId().equals(TemplateFormats.markdown().getId())) { void undocumentedPathParameter(OperationBuilder operationBuilder) {
return input; assertThatExceptionOfType(SnippetException.class)
} .isThrownBy(() -> new PathParametersSnippet(Collections.<ParameterDescriptor>emptyList())
return input.replace("|", "\\|"); .document(operationBuilder.attribute(RestDocumentationGenerator.ATTRIBUTE_NAME_URL_TEMPLATE, "/{a}/")
.build()))
.withMessage("Path parameters with the following names were not documented: [a]");
} }
private String getTitle() { @SnippetTest
return getTitle("/{a}/{b}"); 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) { @SnippetTest
if (this.templateFormat.getId().equals(TemplateFormats.asciidoctor().getId())) { 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 + "+";
} }
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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with 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.io.IOException;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections;
import org.junit.Test; import org.springframework.restdocs.snippet.SnippetException;
import org.springframework.restdocs.testfixtures.jupiter.AssertableSnippets;
import org.springframework.restdocs.AbstractSnippetTests; import org.springframework.restdocs.testfixtures.jupiter.OperationBuilder;
import org.springframework.restdocs.templates.TemplateEngine; import org.springframework.restdocs.testfixtures.jupiter.RenderedSnippetTest;
import org.springframework.restdocs.templates.TemplateFormat; import org.springframework.restdocs.testfixtures.jupiter.SnippetTemplate;
import org.springframework.restdocs.templates.TemplateFormats; import org.springframework.restdocs.testfixtures.jupiter.SnippetTest;
import org.springframework.restdocs.templates.TemplateResourceResolver;
import org.springframework.restdocs.templates.mustache.MustacheTemplateEngine;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.BDDMockito.given; import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.mockito.Mockito.mock;
import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName; import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName;
import static org.springframework.restdocs.snippet.Attributes.attributes; import static org.springframework.restdocs.snippet.Attributes.attributes;
import static org.springframework.restdocs.snippet.Attributes.key; import static org.springframework.restdocs.snippet.Attributes.key;
@@ -40,142 +38,151 @@ import static org.springframework.restdocs.snippet.Attributes.key;
* *
* @author Andy Wilkinson * @author Andy Wilkinson
*/ */
public class QueryParametersSnippetTests extends AbstractSnippetTests { class QueryParametersSnippetTests {
public QueryParametersSnippetTests(String name, TemplateFormat templateFormat) { @RenderedSnippetTest
super(name, templateFormat); void queryParameters(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
}
@Test
public void queryParameters() throws IOException {
new QueryParametersSnippet( new QueryParametersSnippet(
Arrays.asList(parameterWithName("a").description("one"), parameterWithName("b").description("two"))) Arrays.asList(parameterWithName("a").description("one"), parameterWithName("b").description("two")))
.document(this.operationBuilder.request("http://localhost?a=alpha&b=bravo").build()); .document(operationBuilder.request("http://localhost?a=alpha&b=bravo").build());
assertThat(this.generatedSnippets.queryParameters()) assertThat(snippets.queryParameters())
.is(tableWithHeader("Parameter", "Description").row("`a`", "one").row("`b`", "two")); .isTable((table) -> table.withHeader("Parameter", "Description").row("`a`", "one").row("`b`", "two"));
} }
@Test @RenderedSnippetTest
public void queryParameterWithNoValue() throws IOException { void queryParameterWithNoValue(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
new QueryParametersSnippet(Arrays.asList(parameterWithName("a").description("one"))) new QueryParametersSnippet(Arrays.asList(parameterWithName("a").description("one")))
.document(this.operationBuilder.request("http://localhost?a").build()); .document(operationBuilder.request("http://localhost?a").build());
assertThat(this.generatedSnippets.queryParameters()) assertThat(snippets.queryParameters())
.is(tableWithHeader("Parameter", "Description").row("`a`", "one")); .isTable((table) -> table.withHeader("Parameter", "Description").row("`a`", "one"));
} }
@Test @RenderedSnippetTest
public void ignoredQueryParameter() throws IOException { void ignoredQueryParameter(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
new QueryParametersSnippet( new QueryParametersSnippet(
Arrays.asList(parameterWithName("a").ignored(), parameterWithName("b").description("two"))) Arrays.asList(parameterWithName("a").ignored(), parameterWithName("b").description("two")))
.document(this.operationBuilder.request("http://localhost?a=alpha&b=bravo").build()); .document(operationBuilder.request("http://localhost?a=alpha&b=bravo").build());
assertThat(this.generatedSnippets.queryParameters()) assertThat(snippets.queryParameters())
.is(tableWithHeader("Parameter", "Description").row("`b`", "two")); .isTable((table) -> table.withHeader("Parameter", "Description").row("`b`", "two"));
} }
@Test @RenderedSnippetTest
public void allUndocumentedQueryParametersCanBeIgnored() throws IOException { void allUndocumentedQueryParametersCanBeIgnored(OperationBuilder operationBuilder, AssertableSnippets snippets)
throws IOException {
new QueryParametersSnippet(Arrays.asList(parameterWithName("b").description("two")), true) new QueryParametersSnippet(Arrays.asList(parameterWithName("b").description("two")), true)
.document(this.operationBuilder.request("http://localhost?a=alpha&b=bravo").build()); .document(operationBuilder.request("http://localhost?a=alpha&b=bravo").build());
assertThat(this.generatedSnippets.queryParameters()) assertThat(snippets.queryParameters())
.is(tableWithHeader("Parameter", "Description").row("`b`", "two")); .isTable((table) -> table.withHeader("Parameter", "Description").row("`b`", "two"));
} }
@Test @RenderedSnippetTest
public void missingOptionalQueryParameter() throws IOException { void missingOptionalQueryParameter(OperationBuilder operationBuilder, AssertableSnippets snippets)
throws IOException {
new QueryParametersSnippet(Arrays.asList(parameterWithName("a").description("one").optional(), new QueryParametersSnippet(Arrays.asList(parameterWithName("a").description("one").optional(),
parameterWithName("b").description("two"))) parameterWithName("b").description("two")))
.document(this.operationBuilder.request("http://localhost?b=bravo").build()); .document(operationBuilder.request("http://localhost?b=bravo").build());
assertThat(this.generatedSnippets.queryParameters()) assertThat(snippets.queryParameters())
.is(tableWithHeader("Parameter", "Description").row("`a`", "one").row("`b`", "two")); .isTable((table) -> table.withHeader("Parameter", "Description").row("`a`", "one").row("`b`", "two"));
} }
@Test @RenderedSnippetTest
public void presentOptionalQueryParameter() throws IOException { void presentOptionalQueryParameter(OperationBuilder operationBuilder, AssertableSnippets snippets)
throws IOException {
new QueryParametersSnippet(Arrays.asList(parameterWithName("a").description("one").optional())) new QueryParametersSnippet(Arrays.asList(parameterWithName("a").description("one").optional()))
.document(this.operationBuilder.request("http://localhost?a=alpha").build()); .document(operationBuilder.request("http://localhost?a=alpha").build());
assertThat(this.generatedSnippets.queryParameters()) assertThat(snippets.queryParameters())
.is(tableWithHeader("Parameter", "Description").row("`a`", "one")); .isTable((table) -> table.withHeader("Parameter", "Description").row("`a`", "one"));
} }
@Test @RenderedSnippetTest
public void queryParametersWithCustomAttributes() throws IOException { @SnippetTemplate(snippet = "query-parameters", template = "query-parameters-with-title")
TemplateResourceResolver resolver = mock(TemplateResourceResolver.class); void queryParametersWithCustomAttributes(OperationBuilder operationBuilder, AssertableSnippets snippets)
given(resolver.resolveTemplateResource("query-parameters")) throws IOException {
.willReturn(snippetResource("query-parameters-with-title"));
new QueryParametersSnippet( new QueryParametersSnippet(
Arrays.asList(parameterWithName("a").description("one").attributes(key("foo").value("alpha")), Arrays.asList(parameterWithName("a").description("one").attributes(key("foo").value("alpha")),
parameterWithName("b").description("two").attributes(key("foo").value("bravo"))), parameterWithName("b").description("two").attributes(key("foo").value("bravo"))),
attributes(key("title").value("The title"))) attributes(key("title").value("The title")))
.document(this.operationBuilder .document(operationBuilder.request("http://localhost?a=alpha&b=bravo").build());
.attribute(TemplateEngine.class.getName(), new MustacheTemplateEngine(resolver)) assertThat(snippets.queryParameters()).contains("The title");
.request("http://localhost?a=alpha&b=bravo")
.build());
assertThat(this.generatedSnippets.queryParameters()).contains("The title");
} }
@Test @RenderedSnippetTest
public void queryParametersWithCustomDescriptorAttributes() throws IOException { @SnippetTemplate(snippet = "query-parameters", template = "query-parameters-with-extra-column")
TemplateResourceResolver resolver = mock(TemplateResourceResolver.class); void queryParametersWithCustomDescriptorAttributes(OperationBuilder operationBuilder, AssertableSnippets snippets)
given(resolver.resolveTemplateResource("query-parameters")) throws IOException {
.willReturn(snippetResource("query-parameters-with-extra-column"));
new QueryParametersSnippet( new QueryParametersSnippet(
Arrays.asList(parameterWithName("a").description("one").attributes(key("foo").value("alpha")), Arrays.asList(parameterWithName("a").description("one").attributes(key("foo").value("alpha")),
parameterWithName("b").description("two").attributes(key("foo").value("bravo")))) parameterWithName("b").description("two").attributes(key("foo").value("bravo"))))
.document(this.operationBuilder .document(operationBuilder.request("http://localhost?a=alpha&b=bravo").build());
.attribute(TemplateEngine.class.getName(), new MustacheTemplateEngine(resolver)) assertThat(snippets.queryParameters()).isTable((table) -> table.withHeader("Parameter", "Description", "Foo")
.request("http://localhost?a=alpha&b=bravo") .row("a", "one", "alpha")
.build()); .row("b", "two", "bravo"));
assertThat(this.generatedSnippets.queryParameters())
.is(tableWithHeader("Parameter", "Description", "Foo").row("a", "one", "alpha").row("b", "two", "bravo"));
} }
@Test @RenderedSnippetTest
public void queryParametersWithOptionalColumn() throws IOException { @SnippetTemplate(snippet = "query-parameters", template = "query-parameters-with-optional-column")
TemplateResourceResolver resolver = mock(TemplateResourceResolver.class); void queryParametersWithOptionalColumn(OperationBuilder operationBuilder, AssertableSnippets snippets)
given(resolver.resolveTemplateResource("query-parameters")) throws IOException {
.willReturn(snippetResource("query-parameters-with-optional-column"));
new QueryParametersSnippet(Arrays.asList(parameterWithName("a").description("one").optional(), new QueryParametersSnippet(Arrays.asList(parameterWithName("a").description("one").optional(),
parameterWithName("b").description("two"))) parameterWithName("b").description("two")))
.document(this.operationBuilder .document(operationBuilder.request("http://localhost?a=alpha&b=bravo").build());
.attribute(TemplateEngine.class.getName(), new MustacheTemplateEngine(resolver)) assertThat(snippets.queryParameters())
.request("http://localhost?a=alpha&b=bravo") .isTable((table) -> table.withHeader("Parameter", "Optional", "Description")
.build()); .row("a", "true", "one")
assertThat(this.generatedSnippets.queryParameters())
.is(tableWithHeader("Parameter", "Optional", "Description").row("a", "true", "one")
.row("b", "false", "two")); .row("b", "false", "two"));
} }
@Test @RenderedSnippetTest
public void additionalDescriptors() throws IOException { void additionalDescriptors(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
RequestDocumentation.queryParameters(parameterWithName("a").description("one")) RequestDocumentation.queryParameters(parameterWithName("a").description("one"))
.and(parameterWithName("b").description("two")) .and(parameterWithName("b").description("two"))
.document(this.operationBuilder.request("http://localhost?a=alpha&b=bravo").build()); .document(operationBuilder.request("http://localhost?a=alpha&b=bravo").build());
assertThat(this.generatedSnippets.queryParameters()) assertThat(snippets.queryParameters())
.is(tableWithHeader("Parameter", "Description").row("`a`", "one").row("`b`", "two")); .isTable((table) -> table.withHeader("Parameter", "Description").row("`a`", "one").row("`b`", "two"));
} }
@Test @RenderedSnippetTest
public void additionalDescriptorsWithRelaxedQueryParameters() throws IOException { void additionalDescriptorsWithRelaxedQueryParameters(OperationBuilder operationBuilder, AssertableSnippets snippets)
throws IOException {
RequestDocumentation.relaxedQueryParameters(parameterWithName("a").description("one")) RequestDocumentation.relaxedQueryParameters(parameterWithName("a").description("one"))
.and(parameterWithName("b").description("two")) .and(parameterWithName("b").description("two"))
.document(this.operationBuilder.request("http://localhost?a=alpha&b=bravo&c=undocumented").build()); .document(operationBuilder.request("http://localhost?a=alpha&b=bravo&c=undocumented").build());
assertThat(this.generatedSnippets.queryParameters()) assertThat(snippets.queryParameters())
.is(tableWithHeader("Parameter", "Description").row("`a`", "one").row("`b`", "two")); .isTable((table) -> table.withHeader("Parameter", "Description").row("`a`", "one").row("`b`", "two"));
} }
@Test @RenderedSnippetTest
public void queryParametersWithEscapedContent() throws IOException { void queryParametersWithEscapedContent(OperationBuilder operationBuilder, AssertableSnippets snippets)
throws IOException {
RequestDocumentation.queryParameters(parameterWithName("Foo|Bar").description("one|two")) RequestDocumentation.queryParameters(parameterWithName("Foo|Bar").description("one|two"))
.document(this.operationBuilder.request("http://localhost?Foo%7CBar=baz").build()); .document(operationBuilder.request("http://localhost?Foo%7CBar=baz").build());
assertThat(this.generatedSnippets.queryParameters()).is(tableWithHeader("Parameter", "Description") assertThat(snippets.queryParameters())
.row(escapeIfNecessary("`Foo|Bar`"), escapeIfNecessary("one|two"))); .isTable((table) -> table.withHeader("Parameter", "Description").row("`Foo|Bar`", "one|two"));
} }
private String escapeIfNecessary(String input) { @SnippetTest
if (this.templateFormat.getId().equals(TemplateFormats.markdown().getId())) { void undocumentedParameter(OperationBuilder operationBuilder) {
return input; assertThatExceptionOfType(SnippetException.class)
} .isThrownBy(() -> new QueryParametersSnippet(Collections.<ParameterDescriptor>emptyList())
return input.replace("|", "\\|"); .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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with 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.io.IOException;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections;
import org.junit.Test; import org.springframework.restdocs.snippet.SnippetException;
import org.springframework.restdocs.testfixtures.jupiter.AssertableSnippets;
import org.springframework.restdocs.AbstractSnippetTests; import org.springframework.restdocs.testfixtures.jupiter.OperationBuilder;
import org.springframework.restdocs.templates.TemplateEngine; import org.springframework.restdocs.testfixtures.jupiter.RenderedSnippetTest;
import org.springframework.restdocs.templates.TemplateFormat; import org.springframework.restdocs.testfixtures.jupiter.SnippetTemplate;
import org.springframework.restdocs.templates.TemplateFormats; import org.springframework.restdocs.testfixtures.jupiter.SnippetTest;
import org.springframework.restdocs.templates.TemplateResourceResolver;
import org.springframework.restdocs.templates.mustache.MustacheTemplateEngine;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.BDDMockito.given; import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.mockito.Mockito.mock;
import static org.springframework.restdocs.request.RequestDocumentation.partWithName; import static org.springframework.restdocs.request.RequestDocumentation.partWithName;
import static org.springframework.restdocs.snippet.Attributes.attributes; import static org.springframework.restdocs.snippet.Attributes.attributes;
import static org.springframework.restdocs.snippet.Attributes.key; import static org.springframework.restdocs.snippet.Attributes.key;
@@ -40,145 +38,157 @@ import static org.springframework.restdocs.snippet.Attributes.key;
* *
* @author Andy Wilkinson * @author Andy Wilkinson
*/ */
public class RequestPartsSnippetTests extends AbstractSnippetTests { class RequestPartsSnippetTests {
public RequestPartsSnippetTests(String name, TemplateFormat templateFormat) { @RenderedSnippetTest
super(name, templateFormat); void requestParts(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
}
@Test
public void requestParts() throws IOException {
new RequestPartsSnippet( new RequestPartsSnippet(
Arrays.asList(partWithName("a").description("one"), partWithName("b").description("two"))) 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()) .part("a", "bravo".getBytes())
.and() .and()
.part("b", "bravo".getBytes()) .part("b", "bravo".getBytes())
.build()); .build());
assertThat(this.generatedSnippets.requestParts()) assertThat(snippets.requestParts())
.is(tableWithHeader("Part", "Description").row("`a`", "one").row("`b`", "two")); .isTable((table) -> table.withHeader("Part", "Description").row("`a`", "one").row("`b`", "two"));
} }
@Test @RenderedSnippetTest
public void ignoredRequestPart() throws IOException { void ignoredRequestPart(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
new RequestPartsSnippet(Arrays.asList(partWithName("a").ignored(), partWithName("b").description("two"))) 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()) .part("a", "bravo".getBytes())
.and() .and()
.part("b", "bravo".getBytes()) .part("b", "bravo".getBytes())
.build()); .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 @RenderedSnippetTest
public void allUndocumentedRequestPartsCanBeIgnored() throws IOException { void allUndocumentedRequestPartsCanBeIgnored(OperationBuilder operationBuilder, AssertableSnippets snippets)
throws IOException {
new RequestPartsSnippet(Arrays.asList(partWithName("b").description("two")), true) new RequestPartsSnippet(Arrays.asList(partWithName("b").description("two")), true)
.document(this.operationBuilder.request("http://localhost") .document(operationBuilder.request("http://localhost")
.part("a", "bravo".getBytes()) .part("a", "bravo".getBytes())
.and() .and()
.part("b", "bravo".getBytes()) .part("b", "bravo".getBytes())
.build()); .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 @RenderedSnippetTest
public void missingOptionalRequestPart() throws IOException { void missingOptionalRequestPart(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
new RequestPartsSnippet( new RequestPartsSnippet(
Arrays.asList(partWithName("a").description("one").optional(), partWithName("b").description("two"))) Arrays.asList(partWithName("a").description("one").optional(), partWithName("b").description("two")))
.document(this.operationBuilder.request("http://localhost").part("b", "bravo".getBytes()).build()); .document(operationBuilder.request("http://localhost").part("b", "bravo".getBytes()).build());
assertThat(this.generatedSnippets.requestParts()) assertThat(snippets.requestParts())
.is(tableWithHeader("Part", "Description").row("`a`", "one").row("`b`", "two")); .isTable((table) -> table.withHeader("Part", "Description").row("`a`", "one").row("`b`", "two"));
} }
@Test @RenderedSnippetTest
public void presentOptionalRequestPart() throws IOException { void presentOptionalRequestPart(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
new RequestPartsSnippet(Arrays.asList(partWithName("a").description("one").optional())) new RequestPartsSnippet(Arrays.asList(partWithName("a").description("one").optional()))
.document(this.operationBuilder.request("http://localhost").part("a", "one".getBytes()).build()); .document(operationBuilder.request("http://localhost").part("a", "one".getBytes()).build());
assertThat(this.generatedSnippets.requestParts()).is(tableWithHeader("Part", "Description").row("`a`", "one")); assertThat(snippets.requestParts())
.isTable((table) -> table.withHeader("Part", "Description").row("`a`", "one"));
} }
@Test @RenderedSnippetTest
public void requestPartsWithCustomAttributes() throws IOException { @SnippetTemplate(snippet = "request-parts", template = "request-parts-with-title")
TemplateResourceResolver resolver = mock(TemplateResourceResolver.class); void requestPartsWithCustomAttributes(OperationBuilder operationBuilder, AssertableSnippets snippets)
given(resolver.resolveTemplateResource("request-parts")) throws IOException {
.willReturn(snippetResource("request-parts-with-title"));
new RequestPartsSnippet( new RequestPartsSnippet(
Arrays.asList(partWithName("a").description("one").attributes(key("foo").value("alpha")), Arrays.asList(partWithName("a").description("one").attributes(key("foo").value("alpha")),
partWithName("b").description("two").attributes(key("foo").value("bravo"))), partWithName("b").description("two").attributes(key("foo").value("bravo"))),
attributes(key("title").value("The title"))) attributes(key("title").value("The title")))
.document(this.operationBuilder .document(operationBuilder.request("http://localhost")
.attribute(TemplateEngine.class.getName(), new MustacheTemplateEngine(resolver))
.request("http://localhost")
.part("a", "alpha".getBytes()) .part("a", "alpha".getBytes())
.and() .and()
.part("b", "bravo".getBytes()) .part("b", "bravo".getBytes())
.build()); .build());
assertThat(this.generatedSnippets.requestParts()).contains("The title"); assertThat(snippets.requestParts()).contains("The title");
} }
@Test @RenderedSnippetTest
public void requestPartsWithCustomDescriptorAttributes() throws IOException { @SnippetTemplate(snippet = "request-parts", template = "request-parts-with-extra-column")
TemplateResourceResolver resolver = mock(TemplateResourceResolver.class); void requestPartsWithCustomDescriptorAttributes(OperationBuilder operationBuilder, AssertableSnippets snippets)
given(resolver.resolveTemplateResource("request-parts")) throws IOException {
.willReturn(snippetResource("request-parts-with-extra-column"));
new RequestPartsSnippet( new RequestPartsSnippet(
Arrays.asList(partWithName("a").description("one").attributes(key("foo").value("alpha")), Arrays.asList(partWithName("a").description("one").attributes(key("foo").value("alpha")),
partWithName("b").description("two").attributes(key("foo").value("bravo")))) partWithName("b").description("two").attributes(key("foo").value("bravo"))))
.document(this.operationBuilder .document(operationBuilder.request("http://localhost")
.attribute(TemplateEngine.class.getName(), new MustacheTemplateEngine(resolver))
.request("http://localhost")
.part("a", "alpha".getBytes()) .part("a", "alpha".getBytes())
.and() .and()
.part("b", "bravo".getBytes()) .part("b", "bravo".getBytes())
.build()); .build());
assertThat(this.generatedSnippets.requestParts()) assertThat(snippets.requestParts()).isTable((table) -> table.withHeader("Part", "Description", "Foo")
.is(tableWithHeader("Part", "Description", "Foo").row("a", "one", "alpha").row("b", "two", "bravo")); .row("a", "one", "alpha")
.row("b", "two", "bravo"));
} }
@Test @RenderedSnippetTest
public void requestPartsWithOptionalColumn() throws IOException { @SnippetTemplate(snippet = "request-parts", template = "request-parts-with-optional-column")
TemplateResourceResolver resolver = mock(TemplateResourceResolver.class); void requestPartsWithOptionalColumn(OperationBuilder operationBuilder, AssertableSnippets snippets)
given(resolver.resolveTemplateResource("request-parts")) throws IOException {
.willReturn(snippetResource("request-parts-with-optional-column"));
new RequestPartsSnippet( new RequestPartsSnippet(
Arrays.asList(partWithName("a").description("one").optional(), partWithName("b").description("two"))) Arrays.asList(partWithName("a").description("one").optional(), partWithName("b").description("two")))
.document(this.operationBuilder .document(operationBuilder.request("http://localhost")
.attribute(TemplateEngine.class.getName(), new MustacheTemplateEngine(resolver))
.request("http://localhost")
.part("a", "alpha".getBytes()) .part("a", "alpha".getBytes())
.and() .and()
.part("b", "bravo".getBytes()) .part("b", "bravo".getBytes())
.build()); .build());
assertThat(this.generatedSnippets.requestParts()) assertThat(snippets.requestParts()).isTable((table) -> table.withHeader("Part", "Optional", "Description")
.is(tableWithHeader("Part", "Optional", "Description").row("a", "true", "one").row("b", "false", "two")); .row("a", "true", "one")
.row("b", "false", "two"));
} }
@Test @RenderedSnippetTest
public void additionalDescriptors() throws IOException { void additionalDescriptors(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException {
RequestDocumentation.requestParts(partWithName("a").description("one")) RequestDocumentation.requestParts(partWithName("a").description("one"))
.and(partWithName("b").description("two")) .and(partWithName("b").description("two"))
.document(this.operationBuilder.request("http://localhost") .document(operationBuilder.request("http://localhost")
.part("a", "bravo".getBytes()) .part("a", "bravo".getBytes())
.and() .and()
.part("b", "bravo".getBytes()) .part("b", "bravo".getBytes())
.build()); .build());
assertThat(this.generatedSnippets.requestParts()) assertThat(snippets.requestParts())
.is(tableWithHeader("Part", "Description").row("`a`", "one").row("`b`", "two")); .isTable((table) -> table.withHeader("Part", "Description").row("`a`", "one").row("`b`", "two"));
} }
@Test @RenderedSnippetTest
public void requestPartsWithEscapedContent() throws IOException { void requestPartsWithEscapedContent(OperationBuilder operationBuilder, AssertableSnippets snippets)
throws IOException {
RequestDocumentation.requestParts(partWithName("Foo|Bar").description("one|two")) RequestDocumentation.requestParts(partWithName("Foo|Bar").description("one|two"))
.document(this.operationBuilder.request("http://localhost").part("Foo|Bar", "baz".getBytes()).build()); .document(operationBuilder.request("http://localhost").part("Foo|Bar", "baz".getBytes()).build());
assertThat(this.generatedSnippets.requestParts()).is(tableWithHeader("Part", "Description") assertThat(snippets.requestParts())
.row(escapeIfNecessary("`Foo|Bar`"), escapeIfNecessary("one|two"))); .isTable((table) -> table.withHeader("Part", "Description").row("`Foo|Bar`", "one|two"));
} }
private String escapeIfNecessary(String input) { @SnippetTest
if (this.templateFormat.getId().equals(TemplateFormats.markdown().getId())) { void undocumentedPart(OperationBuilder operationBuilder) {
return input; assertThatExceptionOfType(SnippetException.class)
} .isThrownBy(() -> new RequestPartsSnippet(Collections.<RequestPartDescriptor>emptyList())
return input.replace("|", "\\|"); .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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -16,7 +16,7 @@
package org.springframework.restdocs.snippet; package org.springframework.restdocs.snippet;
import org.junit.Test; import org.junit.jupiter.api.Test;
import org.springframework.restdocs.ManualRestDocumentation; import org.springframework.restdocs.ManualRestDocumentation;
import org.springframework.restdocs.RestDocumentationContext; import org.springframework.restdocs.RestDocumentationContext;
@@ -30,82 +30,82 @@ import static org.assertj.core.api.Assertions.assertThat;
* @author Andy Wilkinson * @author Andy Wilkinson
* *
*/ */
public class RestDocumentationContextPlaceholderResolverTests { class RestDocumentationContextPlaceholderResolverTests {
@Test @Test
public void kebabCaseMethodName() { void kebabCaseMethodName() {
assertThat(createResolver("dashSeparatedMethodName").resolvePlaceholder("method-name")) assertThat(createResolver("dashSeparatedMethodName").resolvePlaceholder("method-name"))
.isEqualTo("dash-separated-method-name"); .isEqualTo("dash-separated-method-name");
} }
@Test @Test
public void kebabCaseMethodNameWithUpperCaseOpeningSection() { void kebabCaseMethodNameWithUpperCaseOpeningSection() {
assertThat(createResolver("URIDashSeparatedMethodName").resolvePlaceholder("method-name")) assertThat(createResolver("URIDashSeparatedMethodName").resolvePlaceholder("method-name"))
.isEqualTo("uri-dash-separated-method-name"); .isEqualTo("uri-dash-separated-method-name");
} }
@Test @Test
public void kebabCaseMethodNameWithUpperCaseMidSection() { void kebabCaseMethodNameWithUpperCaseMidSection() {
assertThat(createResolver("dashSeparatedMethodNameWithURIInIt").resolvePlaceholder("method-name")) assertThat(createResolver("dashSeparatedMethodNameWithURIInIt").resolvePlaceholder("method-name"))
.isEqualTo("dash-separated-method-name-with-uri-in-it"); .isEqualTo("dash-separated-method-name-with-uri-in-it");
} }
@Test @Test
public void kebabCaseMethodNameWithUpperCaseEndSection() { void kebabCaseMethodNameWithUpperCaseEndSection() {
assertThat(createResolver("dashSeparatedMethodNameWithURI").resolvePlaceholder("method-name")) assertThat(createResolver("dashSeparatedMethodNameWithURI").resolvePlaceholder("method-name"))
.isEqualTo("dash-separated-method-name-with-uri"); .isEqualTo("dash-separated-method-name-with-uri");
} }
@Test @Test
public void snakeCaseMethodName() { void snakeCaseMethodName() {
assertThat(createResolver("underscoreSeparatedMethodName").resolvePlaceholder("method_name")) assertThat(createResolver("underscoreSeparatedMethodName").resolvePlaceholder("method_name"))
.isEqualTo("underscore_separated_method_name"); .isEqualTo("underscore_separated_method_name");
} }
@Test @Test
public void snakeCaseMethodNameWithUpperCaseOpeningSection() { void snakeCaseMethodNameWithUpperCaseOpeningSection() {
assertThat(createResolver("URIUnderscoreSeparatedMethodName").resolvePlaceholder("method_name")) assertThat(createResolver("URIUnderscoreSeparatedMethodName").resolvePlaceholder("method_name"))
.isEqualTo("uri_underscore_separated_method_name"); .isEqualTo("uri_underscore_separated_method_name");
} }
@Test @Test
public void snakeCaseMethodNameWithUpperCaseMidSection() { void snakeCaseMethodNameWithUpperCaseMidSection() {
assertThat(createResolver("underscoreSeparatedMethodNameWithURIInIt").resolvePlaceholder("method_name")) assertThat(createResolver("underscoreSeparatedMethodNameWithURIInIt").resolvePlaceholder("method_name"))
.isEqualTo("underscore_separated_method_name_with_uri_in_it"); .isEqualTo("underscore_separated_method_name_with_uri_in_it");
} }
@Test @Test
public void snakeCaseMethodNameWithUpperCaseEndSection() { void snakeCaseMethodNameWithUpperCaseEndSection() {
assertThat(createResolver("underscoreSeparatedMethodNameWithURI").resolvePlaceholder("method_name")) assertThat(createResolver("underscoreSeparatedMethodNameWithURI").resolvePlaceholder("method_name"))
.isEqualTo("underscore_separated_method_name_with_uri"); .isEqualTo("underscore_separated_method_name_with_uri");
} }
@Test @Test
public void camelCaseMethodName() { void camelCaseMethodName() {
assertThat(createResolver("camelCaseMethodName").resolvePlaceholder("methodName")) assertThat(createResolver("camelCaseMethodName").resolvePlaceholder("methodName"))
.isEqualTo("camelCaseMethodName"); .isEqualTo("camelCaseMethodName");
} }
@Test @Test
public void kebabCaseClassName() { void kebabCaseClassName() {
assertThat(createResolver().resolvePlaceholder("class-name")) assertThat(createResolver().resolvePlaceholder("class-name"))
.isEqualTo("rest-documentation-context-placeholder-resolver-tests"); .isEqualTo("rest-documentation-context-placeholder-resolver-tests");
} }
@Test @Test
public void snakeCaseClassName() { void snakeCaseClassName() {
assertThat(createResolver().resolvePlaceholder("class_name")) assertThat(createResolver().resolvePlaceholder("class_name"))
.isEqualTo("rest_documentation_context_placeholder_resolver_tests"); .isEqualTo("rest_documentation_context_placeholder_resolver_tests");
} }
@Test @Test
public void camelCaseClassName() { void camelCaseClassName() {
assertThat(createResolver().resolvePlaceholder("ClassName")) assertThat(createResolver().resolvePlaceholder("ClassName"))
.isEqualTo("RestDocumentationContextPlaceholderResolverTests"); .isEqualTo("RestDocumentationContextPlaceholderResolverTests");
} }
@Test @Test
public void stepCount() { void stepCount() {
assertThat(createResolver("stepCount").resolvePlaceholder("step")).isEqualTo("1"); 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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with 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.IOException;
import java.io.Writer; import java.io.Writer;
import org.junit.Rule; import org.junit.jupiter.api.Test;
import org.junit.Test; import org.junit.jupiter.api.io.TempDir;
import org.junit.rules.TemporaryFolder;
import org.springframework.restdocs.ManualRestDocumentation; import org.springframework.restdocs.ManualRestDocumentation;
import org.springframework.restdocs.RestDocumentationContext; import org.springframework.restdocs.RestDocumentationContext;
@@ -40,10 +39,10 @@ import static org.mockito.Mockito.mock;
* *
* @author Andy Wilkinson * @author Andy Wilkinson
*/ */
public class StandardWriterResolverTests { class StandardWriterResolverTests {
@Rule @TempDir
public final TemporaryFolder temp = new TemporaryFolder(); File temp;
private final PlaceholderResolverFactory placeholderResolverFactory = mock(PlaceholderResolverFactory.class); private final PlaceholderResolverFactory placeholderResolverFactory = mock(PlaceholderResolverFactory.class);
@@ -51,21 +50,21 @@ public class StandardWriterResolverTests {
TemplateFormats.asciidoctor()); TemplateFormats.asciidoctor());
@Test @Test
public void absoluteInput() { void absoluteInput() {
String absolutePath = new File("foo").getAbsolutePath(); String absolutePath = new File("foo").getAbsolutePath();
assertThat(this.resolver.resolveFile(absolutePath, "bar.txt", createContext(absolutePath))) assertThat(this.resolver.resolveFile(absolutePath, "bar.txt", createContext(absolutePath)))
.isEqualTo(new File(absolutePath, "bar.txt")); .isEqualTo(new File(absolutePath, "bar.txt"));
} }
@Test @Test
public void configuredOutputAndRelativeInput() { void configuredOutputAndRelativeInput() {
File outputDir = new File("foo").getAbsoluteFile(); File outputDir = new File("foo").getAbsoluteFile();
assertThat(this.resolver.resolveFile("bar", "baz.txt", createContext(outputDir.getAbsolutePath()))) assertThat(this.resolver.resolveFile("bar", "baz.txt", createContext(outputDir.getAbsolutePath())))
.isEqualTo(new File(outputDir, "bar/baz.txt")); .isEqualTo(new File(outputDir, "bar/baz.txt"));
} }
@Test @Test
public void configuredOutputAndAbsoluteInput() { void configuredOutputAndAbsoluteInput() {
File outputDir = new File("foo").getAbsoluteFile(); File outputDir = new File("foo").getAbsoluteFile();
String absolutePath = new File("bar").getAbsolutePath(); String absolutePath = new File("bar").getAbsolutePath();
assertThat(this.resolver.resolveFile(absolutePath, "baz.txt", createContext(outputDir.getAbsolutePath()))) assertThat(this.resolver.resolveFile(absolutePath, "baz.txt", createContext(outputDir.getAbsolutePath())))
@@ -73,8 +72,8 @@ public class StandardWriterResolverTests {
} }
@Test @Test
public void placeholdersAreResolvedInOperationName() throws IOException { void placeholdersAreResolvedInOperationName() throws IOException {
File outputDirectory = this.temp.newFolder(); File outputDirectory = this.temp;
RestDocumentationContext context = createContext(outputDirectory.getAbsolutePath()); RestDocumentationContext context = createContext(outputDirectory.getAbsolutePath());
PlaceholderResolver resolver = mock(PlaceholderResolver.class); PlaceholderResolver resolver = mock(PlaceholderResolver.class);
given(resolver.resolvePlaceholder("a")).willReturn("alpha"); given(resolver.resolvePlaceholder("a")).willReturn("alpha");
@@ -84,8 +83,8 @@ public class StandardWriterResolverTests {
} }
@Test @Test
public void placeholdersAreResolvedInSnippetName() throws IOException { void placeholdersAreResolvedInSnippetName() throws IOException {
File outputDirectory = this.temp.newFolder(); File outputDirectory = this.temp;
RestDocumentationContext context = createContext(outputDirectory.getAbsolutePath()); RestDocumentationContext context = createContext(outputDirectory.getAbsolutePath());
PlaceholderResolver resolver = mock(PlaceholderResolver.class); PlaceholderResolver resolver = mock(PlaceholderResolver.class);
given(resolver.resolvePlaceholder("b")).willReturn("bravo"); 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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with 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.HashMap;
import java.util.Map; import java.util.Map;
import org.junit.Rule; import org.junit.jupiter.api.Test;
import org.junit.Test;
import org.springframework.restdocs.operation.Operation; import org.springframework.restdocs.operation.Operation;
import org.springframework.restdocs.templates.TemplateFormats; import org.springframework.restdocs.testfixtures.jupiter.AssertableSnippets;
import org.springframework.restdocs.testfixtures.GeneratedSnippets; import org.springframework.restdocs.testfixtures.jupiter.OperationBuilder;
import org.springframework.restdocs.testfixtures.OperationBuilder; import org.springframework.restdocs.testfixtures.jupiter.RenderedSnippetTest;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
@@ -36,16 +35,10 @@ import static org.assertj.core.api.Assertions.assertThat;
* *
* @author Andy Wilkinson * @author Andy Wilkinson
*/ */
public class TemplatedSnippetTests { class TemplatedSnippetTests {
@Rule
public OperationBuilder operationBuilder = new OperationBuilder(TemplateFormats.asciidoctor());
@Rule
public GeneratedSnippets snippets = new GeneratedSnippets(TemplateFormats.asciidoctor());
@Test @Test
public void attributesAreCopied() { void attributesAreCopied() {
Map<String, Object> attributes = new HashMap<>(); Map<String, Object> attributes = new HashMap<>();
attributes.put("a", "alpha"); attributes.put("a", "alpha");
TemplatedSnippet snippet = new TestTemplatedSnippet(attributes); TemplatedSnippet snippet = new TestTemplatedSnippet(attributes);
@@ -55,22 +48,23 @@ public class TemplatedSnippetTests {
} }
@Test @Test
public void nullAttributesAreTolerated() { void nullAttributesAreTolerated() {
assertThat(new TestTemplatedSnippet(null).getAttributes()).isNotNull(); assertThat(new TestTemplatedSnippet(null).getAttributes()).isNotNull();
assertThat(new TestTemplatedSnippet(null).getAttributes()).isEmpty(); assertThat(new TestTemplatedSnippet(null).getAttributes()).isEmpty();
} }
@Test @Test
public void snippetName() { void snippetName() {
assertThat(new TestTemplatedSnippet(Collections.<String, Object>emptyMap()).getSnippetName()).isEqualTo("test"); assertThat(new TestTemplatedSnippet(Collections.<String, Object>emptyMap()).getSnippetName()).isEqualTo("test");
} }
@Test @RenderedSnippetTest
public void multipleSnippetsCanBeProducedFromTheSameTemplate() throws IOException { void multipleSnippetsCanBeProducedFromTheSameTemplate(OperationBuilder operationBuilder, AssertableSnippets snippet)
new TestTemplatedSnippet("one", "multiple-snippets").document(this.operationBuilder.build()); throws IOException {
new TestTemplatedSnippet("two", "multiple-snippets").document(this.operationBuilder.build()); new TestTemplatedSnippet("one", "multiple-snippets").document(operationBuilder.build());
assertThat(this.snippets.snippet("multiple-snippets-one")).isNotNull(); new TestTemplatedSnippet("two", "multiple-snippets").document(operationBuilder.build());
assertThat(this.snippets.snippet("multiple-snippets-two")).isNotNull(); assertThat(snippet.named("multiple-snippets-one")).exists();
assertThat(snippet.named("multiple-snippets-two")).exists();
} }
private static class TestTemplatedSnippet extends TemplatedSnippet { 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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with 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.Map;
import java.util.concurrent.Callable; import java.util.concurrent.Callable;
import org.junit.Test; import org.junit.jupiter.api.Test;
import org.springframework.core.io.Resource; import org.springframework.core.io.Resource;
@@ -34,7 +34,7 @@ import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
* *
* @author Andy Wilkinson * @author Andy Wilkinson
*/ */
public class StandardTemplateResourceResolverTests { class StandardTemplateResourceResolverTests {
private final TemplateResourceResolver resolver = new StandardTemplateResourceResolver( private final TemplateResourceResolver resolver = new StandardTemplateResourceResolver(
TemplateFormats.asciidoctor()); TemplateFormats.asciidoctor());
@@ -42,7 +42,7 @@ public class StandardTemplateResourceResolverTests {
private final TestClassLoader classLoader = new TestClassLoader(); private final TestClassLoader classLoader = new TestClassLoader();
@Test @Test
public void formatSpecificCustomSnippetHasHighestPrecedence() throws IOException { void formatSpecificCustomSnippetHasHighestPrecedence() throws IOException {
this.classLoader.addResource("org/springframework/restdocs/templates/asciidoctor/test.snippet", this.classLoader.addResource("org/springframework/restdocs/templates/asciidoctor/test.snippet",
getClass().getResource("test-format-specific-custom.snippet")); getClass().getResource("test-format-specific-custom.snippet"));
this.classLoader.addResource("org/springframework/restdocs/templates/test.snippet", this.classLoader.addResource("org/springframework/restdocs/templates/test.snippet",
@@ -62,7 +62,7 @@ public class StandardTemplateResourceResolverTests {
} }
@Test @Test
public void generalCustomSnippetIsUsedInAbsenceOfFormatSpecificCustomSnippet() throws IOException { void generalCustomSnippetIsUsedInAbsenceOfFormatSpecificCustomSnippet() throws IOException {
this.classLoader.addResource("org/springframework/restdocs/templates/test.snippet", this.classLoader.addResource("org/springframework/restdocs/templates/test.snippet",
getClass().getResource("test-custom.snippet")); getClass().getResource("test-custom.snippet"));
this.classLoader.addResource("org/springframework/restdocs/templates/asciidoctor/default-test.snippet", this.classLoader.addResource("org/springframework/restdocs/templates/asciidoctor/default-test.snippet",
@@ -80,7 +80,7 @@ public class StandardTemplateResourceResolverTests {
} }
@Test @Test
public void defaultSnippetIsUsedInAbsenceOfCustomSnippets() throws Exception { void defaultSnippetIsUsedInAbsenceOfCustomSnippets() throws Exception {
this.classLoader.addResource("org/springframework/restdocs/templates/asciidoctor/default-test.snippet", this.classLoader.addResource("org/springframework/restdocs/templates/asciidoctor/default-test.snippet",
getClass().getResource("test-default.snippet")); getClass().getResource("test-default.snippet"));
Resource snippet = doWithThreadContextClassLoader(this.classLoader, new Callable<Resource>() { Resource snippet = doWithThreadContextClassLoader(this.classLoader, new Callable<Resource>() {
@@ -96,7 +96,7 @@ public class StandardTemplateResourceResolverTests {
} }
@Test @Test
public void failsIfCustomAndDefaultSnippetsDoNotExist() { void failsIfCustomAndDefaultSnippetsDoNotExist() {
assertThatIllegalStateException() assertThatIllegalStateException()
.isThrownBy(() -> doWithThreadContextClassLoader(this.classLoader, .isThrownBy(() -> doWithThreadContextClassLoader(this.classLoader,
() -> StandardTemplateResourceResolverTests.this.resolver.resolveTemplateResource("test"))) () -> 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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with 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.IOException;
import java.io.StringWriter; import java.io.StringWriter;
import org.junit.Test; import org.junit.jupiter.api.Test;
import org.springframework.restdocs.mustache.Template.Fragment; import org.springframework.restdocs.mustache.Template.Fragment;
@@ -32,10 +32,10 @@ import static org.mockito.Mockito.mock;
* *
* @author Andy Wilkinson * @author Andy Wilkinson
*/ */
public class AsciidoctorTableCellContentLambdaTests { class AsciidoctorTableCellContentLambdaTests {
@Test @Test
public void verticalBarCharactersAreEscaped() throws IOException { void verticalBarCharactersAreEscaped() throws IOException {
Fragment fragment = mock(Fragment.class); Fragment fragment = mock(Fragment.class);
given(fragment.execute()).willReturn("|foo|bar|baz|"); given(fragment.execute()).willReturn("|foo|bar|baz|");
StringWriter writer = new StringWriter(); StringWriter writer = new StringWriter();
@@ -44,7 +44,7 @@ public class AsciidoctorTableCellContentLambdaTests {
} }
@Test @Test
public void escapedVerticalBarCharactersAreNotEscapedAgain() throws IOException { void escapedVerticalBarCharactersAreNotEscapedAgain() throws IOException {
Fragment fragment = mock(Fragment.class); Fragment fragment = mock(Fragment.class);
given(fragment.execute()).willReturn("\\|foo|bar\\|baz|"); given(fragment.execute()).willReturn("\\|foo|bar\\|baz|");
StringWriter writer = new StringWriter(); StringWriter writer = new StringWriter();

View File

@@ -1,4 +1,5 @@
{{title}} {{title}}
Path | Type | Description Path | Type | Description
---- | ---- | ----------- ---- | ---- | -----------
{{#fields}} {{#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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -14,16 +14,11 @@
* limitations under the License. * 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} * 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 * output that has been captured.
* assertions using AssertJ. For example: <pre class="code">
* assertThat(output).contains("started"); // Checks all output
* assertThat(output.getErr()).contains("failed"); // Only checks System.err
* assertThat(output.getOut()).contains("ok"); // Only checks System.out
* </pre>
* *
* @author Madhura Bhave * @author Madhura Bhave
* @author Phillip Webb * @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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package org.springframework.restdocs.testfixtures; package org.springframework.restdocs.testfixtures.jupiter;
import java.io.File; import java.io.File;
import java.net.URI; import java.net.URI;
@@ -26,8 +26,6 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import org.junit.runners.model.Statement;
import org.springframework.http.HttpHeaders; import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod; import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
@@ -59,21 +57,27 @@ import org.springframework.restdocs.templates.mustache.MustacheTemplateEngine;
* *
* @author Andy Wilkinson * @author Andy Wilkinson
*/ */
public class OperationBuilder extends OperationTestRule { public class OperationBuilder {
private final Map<String, Object> attributes = new HashMap<>(); private final Map<String, Object> attributes = new HashMap<>();
private OperationResponseBuilder responseBuilder; private final File outputDirectory;
private String name; private final String name;
private File outputDirectory;
private final TemplateFormat templateFormat; private final TemplateFormat templateFormat;
private OperationResponseBuilder responseBuilder;
private OperationRequestBuilder requestBuilder; 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; this.templateFormat = templateFormat;
} }
@@ -92,13 +96,6 @@ public class OperationBuilder extends OperationTestRule {
return this; return this;
} }
private void prepare(String operationName, File outputDirectory) {
this.name = operationName;
this.outputDirectory = outputDirectory;
this.requestBuilder = null;
this.attributes.clear();
}
public Operation build() { public Operation build() {
if (this.attributes.get(TemplateEngine.class.getName()) == null) { if (this.attributes.get(TemplateEngine.class.getName()) == null) {
Map<String, Object> templateContext = new HashMap<>(); Map<String, Object> templateContext = new HashMap<>();
@@ -127,12 +124,6 @@ public class OperationBuilder extends OperationTestRule {
return context; 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}. * 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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package org.springframework.restdocs.testfixtures; package org.springframework.restdocs.testfixtures.jupiter;
import java.io.IOException; import java.io.IOException;
import java.io.OutputStream; import java.io.OutputStream;
@@ -35,8 +35,6 @@ import org.springframework.util.Assert;
* @author Madhura Bhave * @author Madhura Bhave
* @author Phillip Webb * @author Phillip Webb
* @author Andy Wilkinson * @author Andy Wilkinson
* @author Sam Brannen
* @see OutputCaptureRule
*/ */
class OutputCapture implements CapturedOutput { class OutputCapture implements CapturedOutput {
@@ -61,7 +59,7 @@ class OutputCapture implements CapturedOutput {
if (obj == this) { if (obj == this) {
return true; return true;
} }
if (obj instanceof CapturedOutput || obj instanceof CharSequence) { if (obj instanceof CharSequence) {
return getAll().equals(obj.toString()); return getAll().equals(obj.toString());
} }
return false; 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}. * System.err}.
*/ */
private static class SystemCapture { private static class SystemCapture {
private final Object monitor = new Object();
private final PrintStreamCapture out; private final PrintStreamCapture out;
private final PrintStreamCapture err; private final PrintStreamCapture err;
private final Object monitor = new Object();
private final List<CapturedString> capturedStrings = new ArrayList<>(); private final List<CapturedString> capturedStrings = new ArrayList<>();
SystemCapture() { SystemCapture() {
@@ -195,8 +193,8 @@ class OutputCapture implements CapturedOutput {
} }
private static PrintStream getSystemStream(PrintStream printStream) { private static PrintStream getSystemStream(PrintStream printStream) {
while (printStream instanceof PrintStreamCapture) { while (printStream instanceof PrintStreamCapture printStreamCapture) {
printStream = ((PrintStreamCapture) printStream).getParent(); printStream = printStreamCapture.getParent();
} }
return printStream; 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"))) internal(platform(project(":spring-restdocs-platform")))
testImplementation(testFixtures(project(":spring-restdocs-core"))) testImplementation(testFixtures(project(":spring-restdocs-core")))
testImplementation("junit:junit") }
testImplementation("org.assertj:assertj-core")
testImplementation("org.hamcrest:hamcrest-library") tasks.named("test") {
testImplementation("org.mockito:mockito-core") useJUnitPlatform()
} }

View File

@@ -23,7 +23,7 @@ import java.util.Arrays;
import java.util.Iterator; import java.util.Iterator;
import jakarta.servlet.http.Part; import jakarta.servlet.http.Part;
import org.junit.Test; import org.junit.jupiter.api.Test;
import org.springframework.http.HttpMethod; import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
@@ -46,19 +46,19 @@ import static org.mockito.Mockito.mock;
* *
* @author Andy Wilkinson * @author Andy Wilkinson
*/ */
public class MockMvcRequestConverterTests { class MockMvcRequestConverterTests {
private final MockMvcRequestConverter factory = new MockMvcRequestConverter(); private final MockMvcRequestConverter factory = new MockMvcRequestConverter();
@Test @Test
public void httpRequest() { void httpRequest() {
OperationRequest request = createOperationRequest(MockMvcRequestBuilders.get("/foo")); OperationRequest request = createOperationRequest(MockMvcRequestBuilders.get("/foo"));
assertThat(request.getUri()).isEqualTo(URI.create("http://localhost/foo")); assertThat(request.getUri()).isEqualTo(URI.create("http://localhost/foo"));
assertThat(request.getMethod()).isEqualTo(HttpMethod.GET); assertThat(request.getMethod()).isEqualTo(HttpMethod.GET);
} }
@Test @Test
public void httpRequestWithCustomPort() { void httpRequestWithCustomPort() {
MockHttpServletRequest mockRequest = MockMvcRequestBuilders.get("/foo").buildRequest(new MockServletContext()); MockHttpServletRequest mockRequest = MockMvcRequestBuilders.get("/foo").buildRequest(new MockServletContext());
mockRequest.setServerPort(8080); mockRequest.setServerPort(8080);
OperationRequest request = this.factory.convert(mockRequest); OperationRequest request = this.factory.convert(mockRequest);
@@ -67,14 +67,14 @@ public class MockMvcRequestConverterTests {
} }
@Test @Test
public void requestWithContextPath() { void requestWithContextPath() {
OperationRequest request = createOperationRequest(MockMvcRequestBuilders.get("/foo/bar").contextPath("/foo")); OperationRequest request = createOperationRequest(MockMvcRequestBuilders.get("/foo/bar").contextPath("/foo"));
assertThat(request.getUri()).isEqualTo(URI.create("http://localhost/foo/bar")); assertThat(request.getUri()).isEqualTo(URI.create("http://localhost/foo/bar"));
assertThat(request.getMethod()).isEqualTo(HttpMethod.GET); assertThat(request.getMethod()).isEqualTo(HttpMethod.GET);
} }
@Test @Test
public void requestWithHeaders() { void requestWithHeaders() {
OperationRequest request = createOperationRequest( OperationRequest request = createOperationRequest(
MockMvcRequestBuilders.get("/foo").header("a", "alpha", "apple").header("b", "bravo")); MockMvcRequestBuilders.get("/foo").header("a", "alpha", "apple").header("b", "bravo"));
assertThat(request.getUri()).isEqualTo(URI.create("http://localhost/foo")); assertThat(request.getUri()).isEqualTo(URI.create("http://localhost/foo"));
@@ -84,7 +84,7 @@ public class MockMvcRequestConverterTests {
} }
@Test @Test
public void requestWithCookies() { void requestWithCookies() {
OperationRequest request = createOperationRequest(MockMvcRequestBuilders.get("/foo") OperationRequest request = createOperationRequest(MockMvcRequestBuilders.get("/foo")
.cookie(new jakarta.servlet.http.Cookie("cookieName1", "cookieVal1"), .cookie(new jakarta.servlet.http.Cookie("cookieName1", "cookieVal1"),
new jakarta.servlet.http.Cookie("cookieName2", "cookieVal2"))); new jakarta.servlet.http.Cookie("cookieName2", "cookieVal2")));
@@ -104,7 +104,7 @@ public class MockMvcRequestConverterTests {
} }
@Test @Test
public void httpsRequest() { void httpsRequest() {
MockHttpServletRequest mockRequest = MockMvcRequestBuilders.get("/foo").buildRequest(new MockServletContext()); MockHttpServletRequest mockRequest = MockMvcRequestBuilders.get("/foo").buildRequest(new MockServletContext());
mockRequest.setScheme("https"); mockRequest.setScheme("https");
mockRequest.setServerPort(443); mockRequest.setServerPort(443);
@@ -114,7 +114,7 @@ public class MockMvcRequestConverterTests {
} }
@Test @Test
public void httpsRequestWithCustomPort() { void httpsRequestWithCustomPort() {
MockHttpServletRequest mockRequest = MockMvcRequestBuilders.get("/foo").buildRequest(new MockServletContext()); MockHttpServletRequest mockRequest = MockMvcRequestBuilders.get("/foo").buildRequest(new MockServletContext());
mockRequest.setScheme("https"); mockRequest.setScheme("https");
mockRequest.setServerPort(8443); mockRequest.setServerPort(8443);
@@ -124,7 +124,7 @@ public class MockMvcRequestConverterTests {
} }
@Test @Test
public void getRequestWithParametersProducesUriWithQueryString() { void getRequestWithParametersProducesUriWithQueryString() {
OperationRequest request = createOperationRequest( OperationRequest request = createOperationRequest(
MockMvcRequestBuilders.get("/foo").param("a", "alpha", "apple").param("b", "br&vo")); 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")); assertThat(request.getUri()).isEqualTo(URI.create("http://localhost/foo?a=alpha&a=apple&b=br%26vo"));
@@ -132,7 +132,7 @@ public class MockMvcRequestConverterTests {
} }
@Test @Test
public void getRequestWithQueryString() { void getRequestWithQueryString() {
MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.get("/foo?a=alpha&b=bravo"); MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.get("/foo?a=alpha&b=bravo");
OperationRequest request = createOperationRequest(builder); OperationRequest request = createOperationRequest(builder);
assertThat(request.getUri()).isEqualTo(URI.create("http://localhost/foo?a=alpha&b=bravo")); assertThat(request.getUri()).isEqualTo(URI.create("http://localhost/foo?a=alpha&b=bravo"));
@@ -140,7 +140,7 @@ public class MockMvcRequestConverterTests {
} }
@Test @Test
public void postRequestWithParametersCreatesFormUrlEncodedContent() { void postRequestWithParametersCreatesFormUrlEncodedContent() {
OperationRequest request = createOperationRequest( OperationRequest request = createOperationRequest(
MockMvcRequestBuilders.post("/foo").param("a", "alpha", "apple").param("b", "br&vo")); MockMvcRequestBuilders.post("/foo").param("a", "alpha", "apple").param("b", "br&vo"));
assertThat(request.getUri()).isEqualTo(URI.create("http://localhost/foo")); assertThat(request.getUri()).isEqualTo(URI.create("http://localhost/foo"));
@@ -150,7 +150,7 @@ public class MockMvcRequestConverterTests {
} }
@Test @Test
public void postRequestWithParametersAndQueryStringCreatesFormUrlEncodedContentWithoutDuplication() { void postRequestWithParametersAndQueryStringCreatesFormUrlEncodedContentWithoutDuplication() {
OperationRequest request = createOperationRequest( OperationRequest request = createOperationRequest(
MockMvcRequestBuilders.post("/foo?a=alpha").param("a", "apple").param("b", "br&vo")); MockMvcRequestBuilders.post("/foo?a=alpha").param("a", "apple").param("b", "br&vo"));
assertThat(request.getUri()).isEqualTo(URI.create("http://localhost/foo?a=alpha")); assertThat(request.getUri()).isEqualTo(URI.create("http://localhost/foo?a=alpha"));
@@ -160,7 +160,7 @@ public class MockMvcRequestConverterTests {
} }
@Test @Test
public void mockMultipartFileUpload() { void mockMultipartFileUpload() {
OperationRequest request = createOperationRequest(MockMvcRequestBuilders.multipart("/foo") OperationRequest request = createOperationRequest(MockMvcRequestBuilders.multipart("/foo")
.file(new MockMultipartFile("file", new byte[] { 1, 2, 3, 4 }))); .file(new MockMultipartFile("file", new byte[] { 1, 2, 3, 4 })));
assertThat(request.getUri()).isEqualTo(URI.create("http://localhost/foo")); assertThat(request.getUri()).isEqualTo(URI.create("http://localhost/foo"));
@@ -175,7 +175,7 @@ public class MockMvcRequestConverterTests {
} }
@Test @Test
public void mockMultipartFileUploadWithContentType() { void mockMultipartFileUploadWithContentType() {
OperationRequest request = createOperationRequest(MockMvcRequestBuilders.multipart("/foo") OperationRequest request = createOperationRequest(MockMvcRequestBuilders.multipart("/foo")
.file(new MockMultipartFile("file", "original", "image/png", new byte[] { 1, 2, 3, 4 }))); .file(new MockMultipartFile("file", "original", "image/png", new byte[] { 1, 2, 3, 4 })));
assertThat(request.getUri()).isEqualTo(URI.create("http://localhost/foo")); assertThat(request.getUri()).isEqualTo(URI.create("http://localhost/foo"));
@@ -189,7 +189,7 @@ public class MockMvcRequestConverterTests {
} }
@Test @Test
public void requestWithPart() throws IOException { void requestWithPart() throws IOException {
MockHttpServletRequest mockRequest = MockMvcRequestBuilders.get("/foo").buildRequest(new MockServletContext()); MockHttpServletRequest mockRequest = MockMvcRequestBuilders.get("/foo").buildRequest(new MockServletContext());
Part mockPart = mock(Part.class); Part mockPart = mock(Part.class);
given(mockPart.getHeaderNames()).willReturn(Arrays.asList("a", "b")); given(mockPart.getHeaderNames()).willReturn(Arrays.asList("a", "b"));
@@ -211,7 +211,7 @@ public class MockMvcRequestConverterTests {
} }
@Test @Test
public void requestWithPartWithContentType() throws IOException { void requestWithPartWithContentType() throws IOException {
MockHttpServletRequest mockRequest = MockMvcRequestBuilders.get("/foo").buildRequest(new MockServletContext()); MockHttpServletRequest mockRequest = MockMvcRequestBuilders.get("/foo").buildRequest(new MockServletContext());
Part mockPart = mock(Part.class); Part mockPart = mock(Part.class);
given(mockPart.getHeaderNames()).willReturn(Arrays.asList("a", "b")); 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.Cookie;
import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpServletResponse;
import org.junit.Test; import org.junit.jupiter.api.Test;
import org.springframework.http.HttpHeaders; import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
@@ -37,12 +37,12 @@ import static org.assertj.core.api.Assertions.entry;
* *
* @author Tomasz Kopczynski * @author Tomasz Kopczynski
*/ */
public class MockMvcResponseConverterTests { class MockMvcResponseConverterTests {
private final MockMvcResponseConverter factory = new MockMvcResponseConverter(); private final MockMvcResponseConverter factory = new MockMvcResponseConverter();
@Test @Test
public void basicResponse() { void basicResponse() {
MockHttpServletResponse response = new MockHttpServletResponse(); MockHttpServletResponse response = new MockHttpServletResponse();
response.setStatus(HttpServletResponse.SC_OK); response.setStatus(HttpServletResponse.SC_OK);
OperationResponse operationResponse = this.factory.convert(response); OperationResponse operationResponse = this.factory.convert(response);
@@ -50,7 +50,7 @@ public class MockMvcResponseConverterTests {
} }
@Test @Test
public void responseWithCookie() { void responseWithCookie() {
MockHttpServletResponse response = new MockHttpServletResponse(); MockHttpServletResponse response = new MockHttpServletResponse();
response.setStatus(HttpServletResponse.SC_OK); response.setStatus(HttpServletResponse.SC_OK);
Cookie cookie = new Cookie("name", "value"); Cookie cookie = new Cookie("name", "value");
@@ -66,7 +66,7 @@ public class MockMvcResponseConverterTests {
} }
@Test @Test
public void responseWithCustomStatus() { void responseWithCustomStatus() {
MockHttpServletResponse response = new MockHttpServletResponse(); MockHttpServletResponse response = new MockHttpServletResponse();
response.setStatus(600); response.setStatus(600);
OperationResponse operationResponse = this.factory.convert(response); 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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with 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.lang.reflect.Method;
import java.util.Map; import java.util.Map;
import org.junit.Assume; import org.junit.jupiter.api.Assumptions;
import org.junit.Rule; import org.junit.jupiter.api.Test;
import org.junit.Test; import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.mock.web.MockHttpServletRequest; 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.restdocs.generate.RestDocumentationGenerator;
import org.springframework.test.web.servlet.request.RequestPostProcessor; import org.springframework.test.web.servlet.request.RequestPostProcessor;
import org.springframework.util.ReflectionUtils; import org.springframework.util.ReflectionUtils;
@@ -41,24 +42,22 @@ import static org.assertj.core.api.Assertions.assertThat;
* @author Andy Wilkinson * @author Andy Wilkinson
* @author Dmitriy Mayboroda * @author Dmitriy Mayboroda
*/ */
public class MockMvcRestDocumentationConfigurerTests { @ExtendWith(RestDocumentationExtension.class)
class MockMvcRestDocumentationConfigurerTests {
private MockHttpServletRequest request = new MockHttpServletRequest(); private MockHttpServletRequest request = new MockHttpServletRequest();
@Rule
public JUnitRestDocumentation restDocumentation = new JUnitRestDocumentation();
@Test @Test
public void defaultConfiguration() { void defaultConfiguration(RestDocumentationContextProvider restDocumentation) {
RequestPostProcessor postProcessor = new MockMvcRestDocumentationConfigurer(this.restDocumentation) RequestPostProcessor postProcessor = new MockMvcRestDocumentationConfigurer(restDocumentation)
.beforeMockMvcCreated(null, null); .beforeMockMvcCreated(null, null);
postProcessor.postProcessRequest(this.request); postProcessor.postProcessRequest(this.request);
assertUriConfiguration("http", "localhost", 8080); assertUriConfiguration("http", "localhost", 8080);
} }
@Test @Test
public void customScheme() { void customScheme(RestDocumentationContextProvider restDocumentation) {
RequestPostProcessor postProcessor = new MockMvcRestDocumentationConfigurer(this.restDocumentation).uris() RequestPostProcessor postProcessor = new MockMvcRestDocumentationConfigurer(restDocumentation).uris()
.withScheme("https") .withScheme("https")
.beforeMockMvcCreated(null, null); .beforeMockMvcCreated(null, null);
postProcessor.postProcessRequest(this.request); postProcessor.postProcessRequest(this.request);
@@ -66,8 +65,8 @@ public class MockMvcRestDocumentationConfigurerTests {
} }
@Test @Test
public void customHost() { void customHost(RestDocumentationContextProvider restDocumentation) {
RequestPostProcessor postProcessor = new MockMvcRestDocumentationConfigurer(this.restDocumentation).uris() RequestPostProcessor postProcessor = new MockMvcRestDocumentationConfigurer(restDocumentation).uris()
.withHost("api.example.com") .withHost("api.example.com")
.beforeMockMvcCreated(null, null); .beforeMockMvcCreated(null, null);
postProcessor.postProcessRequest(this.request); postProcessor.postProcessRequest(this.request);
@@ -75,8 +74,8 @@ public class MockMvcRestDocumentationConfigurerTests {
} }
@Test @Test
public void customPort() { void customPort(RestDocumentationContextProvider restDocumentation) {
RequestPostProcessor postProcessor = new MockMvcRestDocumentationConfigurer(this.restDocumentation).uris() RequestPostProcessor postProcessor = new MockMvcRestDocumentationConfigurer(restDocumentation).uris()
.withPort(8081) .withPort(8081)
.beforeMockMvcCreated(null, null); .beforeMockMvcCreated(null, null);
postProcessor.postProcessRequest(this.request); postProcessor.postProcessRequest(this.request);
@@ -84,8 +83,8 @@ public class MockMvcRestDocumentationConfigurerTests {
} }
@Test @Test
public void noContentLengthHeaderWhenRequestHasNotContent() { void noContentLengthHeaderWhenRequestHasNotContent(RestDocumentationContextProvider restDocumentation) {
RequestPostProcessor postProcessor = new MockMvcRestDocumentationConfigurer(this.restDocumentation).uris() RequestPostProcessor postProcessor = new MockMvcRestDocumentationConfigurer(restDocumentation).uris()
.withPort(8081) .withPort(8081)
.beforeMockMvcCreated(null, null); .beforeMockMvcCreated(null, null);
postProcessor.postProcessRequest(this.request); postProcessor.postProcessRequest(this.request);
@@ -94,8 +93,8 @@ public class MockMvcRestDocumentationConfigurerTests {
@Test @Test
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public void uriTemplateFromRequestAttribute() { void uriTemplateFromRequestAttribute(RestDocumentationContextProvider restDocumentation) {
RequestPostProcessor postProcessor = new MockMvcRestDocumentationConfigurer(this.restDocumentation) RequestPostProcessor postProcessor = new MockMvcRestDocumentationConfigurer(restDocumentation)
.beforeMockMvcCreated(null, null); .beforeMockMvcCreated(null, null);
this.request.setAttribute(RestDocumentationGenerator.ATTRIBUTE_NAME_URL_TEMPLATE, "{a}/{b}"); this.request.setAttribute(RestDocumentationGenerator.ATTRIBUTE_NAME_URL_TEMPLATE, "{a}/{b}");
postProcessor.postProcessRequest(this.request); postProcessor.postProcessRequest(this.request);
@@ -106,11 +105,11 @@ public class MockMvcRestDocumentationConfigurerTests {
@Test @Test
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public void uriTemplateFromRequest() { void uriTemplateFromRequest(RestDocumentationContextProvider restDocumentation) {
Method setUriTemplate = ReflectionUtils.findMethod(MockHttpServletRequest.class, "setUriTemplate", Method setUriTemplate = ReflectionUtils.findMethod(MockHttpServletRequest.class, "setUriTemplate",
String.class); String.class);
Assume.assumeNotNull(setUriTemplate); Assumptions.assumeFalse(setUriTemplate == null);
RequestPostProcessor postProcessor = new MockMvcRestDocumentationConfigurer(this.restDocumentation) RequestPostProcessor postProcessor = new MockMvcRestDocumentationConfigurer(restDocumentation)
.beforeMockMvcCreated(null, null); .beforeMockMvcCreated(null, null);
ReflectionUtils.invokeMethod(setUriTemplate, this.request, "{a}/{b}"); ReflectionUtils.invokeMethod(setUriTemplate, this.request, "{a}/{b}");
postProcessor.postProcessRequest(this.request); 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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with 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.Cookie;
import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpServletResponse;
import org.assertj.core.api.Condition; import org.assertj.core.api.Condition;
import org.junit.After; import org.junit.jupiter.api.AfterEach;
import org.junit.Before; import org.junit.jupiter.api.BeforeEach;
import org.junit.Rule; import org.junit.jupiter.api.Test;
import org.junit.Test; import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
@@ -47,7 +46,8 @@ import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity; 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.mockmvc.MockMvcRestDocumentationIntegrationTests.TestConfiguration;
import org.springframework.restdocs.templates.TemplateFormat; import org.springframework.restdocs.templates.TemplateFormat;
import org.springframework.restdocs.templates.TemplateFormats; 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.HttpRequestCondition;
import org.springframework.restdocs.testfixtures.SnippetConditions.HttpResponseCondition; import org.springframework.restdocs.testfixtures.SnippetConditions.HttpResponseCondition;
import org.springframework.test.context.ContextConfiguration; 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.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult; import org.springframework.test.web.servlet.MvcResult;
@@ -106,29 +106,30 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
* @author Tomasz Kopczynski * @author Tomasz Kopczynski
* @author Filip Hrisafov * @author Filip Hrisafov
*/ */
@RunWith(SpringJUnit4ClassRunner.class) @SpringJUnitConfig
@WebAppConfiguration @WebAppConfiguration
@ExtendWith(RestDocumentationExtension.class)
@ContextConfiguration(classes = TestConfiguration.class) @ContextConfiguration(classes = TestConfiguration.class)
public class MockMvcRestDocumentationIntegrationTests { public class MockMvcRestDocumentationIntegrationTests {
@Rule private RestDocumentationContextProvider restDocumentation;
public JUnitRestDocumentation restDocumentation = new JUnitRestDocumentation();
@Autowired @Autowired
private WebApplicationContext context; private WebApplicationContext context;
@Before @BeforeEach
public void deleteSnippets() { void setUp(RestDocumentationContextProvider restDocumentation) {
this.restDocumentation = restDocumentation;
FileSystemUtils.deleteRecursively(new File("build/generated-snippets")); FileSystemUtils.deleteRecursively(new File("build/generated-snippets"));
} }
@After @AfterEach
public void clearOutputDirSystemProperty() { void clearOutputDirSystemProperty() {
System.clearProperty("org.springframework.restdocs.outputDir"); System.clearProperty("org.springframework.restdocs.outputDir");
} }
@Test @Test
public void basicSnippetGeneration() throws Exception { void basicSnippetGeneration() throws Exception {
MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.context) MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.context)
.apply(new MockMvcRestDocumentationConfigurer(this.restDocumentation).snippets().withEncoding("UTF-8")) .apply(new MockMvcRestDocumentationConfigurer(this.restDocumentation).snippets().withEncoding("UTF-8"))
.build(); .build();
@@ -140,7 +141,19 @@ public class MockMvcRestDocumentationIntegrationTests {
} }
@Test @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) MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.context)
.apply(new MockMvcRestDocumentationConfigurer(this.restDocumentation).snippets() .apply(new MockMvcRestDocumentationConfigurer(this.restDocumentation).snippets()
.withEncoding("UTF-8") .withEncoding("UTF-8")
@@ -154,7 +167,7 @@ public class MockMvcRestDocumentationIntegrationTests {
} }
@Test @Test
public void curlSnippetWithContent() throws Exception { void curlSnippetWithContent() throws Exception {
MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.context) MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.context)
.apply(documentationConfiguration(this.restDocumentation)) .apply(documentationConfiguration(this.restDocumentation))
.build(); .build();
@@ -168,7 +181,7 @@ public class MockMvcRestDocumentationIntegrationTests {
} }
@Test @Test
public void curlSnippetWithCookies() throws Exception { void curlSnippetWithCookies() throws Exception {
MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.context) MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.context)
.apply(documentationConfiguration(this.restDocumentation)) .apply(documentationConfiguration(this.restDocumentation))
.build(); .build();
@@ -182,7 +195,7 @@ public class MockMvcRestDocumentationIntegrationTests {
} }
@Test @Test
public void curlSnippetWithQueryStringOnPost() throws Exception { void curlSnippetWithQueryStringOnPost() throws Exception {
MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.context) MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.context)
.apply(documentationConfiguration(this.restDocumentation)) .apply(documentationConfiguration(this.restDocumentation))
.build(); .build();
@@ -196,7 +209,7 @@ public class MockMvcRestDocumentationIntegrationTests {
} }
@Test @Test
public void curlSnippetWithEmptyParameterQueryString() throws Exception { void curlSnippetWithEmptyParameterQueryString() throws Exception {
MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.context) MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.context)
.apply(documentationConfiguration(this.restDocumentation)) .apply(documentationConfiguration(this.restDocumentation))
.build(); .build();
@@ -210,7 +223,7 @@ public class MockMvcRestDocumentationIntegrationTests {
} }
@Test @Test
public void curlSnippetWithContentAndParametersOnPost() throws Exception { void curlSnippetWithContentAndParametersOnPost() throws Exception {
MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.context) MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.context)
.apply(documentationConfiguration(this.restDocumentation)) .apply(documentationConfiguration(this.restDocumentation))
.build(); .build();
@@ -224,7 +237,7 @@ public class MockMvcRestDocumentationIntegrationTests {
} }
@Test @Test
public void httpieSnippetWithContent() throws Exception { void httpieSnippetWithContent() throws Exception {
MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.context) MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.context)
.apply(documentationConfiguration(this.restDocumentation)) .apply(documentationConfiguration(this.restDocumentation))
.build(); .build();
@@ -237,7 +250,7 @@ public class MockMvcRestDocumentationIntegrationTests {
} }
@Test @Test
public void httpieSnippetWithCookies() throws Exception { void httpieSnippetWithCookies() throws Exception {
MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.context) MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.context)
.apply(documentationConfiguration(this.restDocumentation)) .apply(documentationConfiguration(this.restDocumentation))
.build(); .build();
@@ -251,7 +264,7 @@ public class MockMvcRestDocumentationIntegrationTests {
} }
@Test @Test
public void httpieSnippetWithQueryStringOnPost() throws Exception { void httpieSnippetWithQueryStringOnPost() throws Exception {
MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.context) MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.context)
.apply(documentationConfiguration(this.restDocumentation)) .apply(documentationConfiguration(this.restDocumentation))
.build(); .build();
@@ -265,7 +278,7 @@ public class MockMvcRestDocumentationIntegrationTests {
} }
@Test @Test
public void httpieSnippetWithContentAndParametersOnPost() throws Exception { void httpieSnippetWithContentAndParametersOnPost() throws Exception {
MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.context) MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.context)
.apply(documentationConfiguration(this.restDocumentation)) .apply(documentationConfiguration(this.restDocumentation))
.build(); .build();
@@ -280,7 +293,7 @@ public class MockMvcRestDocumentationIntegrationTests {
} }
@Test @Test
public void linksSnippet() throws Exception { void linksSnippet() throws Exception {
MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.context) MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.context)
.apply(documentationConfiguration(this.restDocumentation)) .apply(documentationConfiguration(this.restDocumentation))
.build(); .build();
@@ -293,7 +306,7 @@ public class MockMvcRestDocumentationIntegrationTests {
} }
@Test @Test
public void pathParametersSnippet() throws Exception { void pathParametersSnippet() throws Exception {
MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.context) MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.context)
.apply(documentationConfiguration(this.restDocumentation)) .apply(documentationConfiguration(this.restDocumentation))
.build(); .build();
@@ -305,7 +318,7 @@ public class MockMvcRestDocumentationIntegrationTests {
} }
@Test @Test
public void queryParametersSnippet() throws Exception { void queryParametersSnippet() throws Exception {
MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.context) MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.context)
.apply(documentationConfiguration(this.restDocumentation)) .apply(documentationConfiguration(this.restDocumentation))
.build(); .build();
@@ -317,7 +330,7 @@ public class MockMvcRestDocumentationIntegrationTests {
} }
@Test @Test
public void requestFieldsSnippet() throws Exception { void requestFieldsSnippet() throws Exception {
MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.context) MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.context)
.apply(documentationConfiguration(this.restDocumentation)) .apply(documentationConfiguration(this.restDocumentation))
.build(); .build();
@@ -329,7 +342,7 @@ public class MockMvcRestDocumentationIntegrationTests {
} }
@Test @Test
public void requestPartsSnippet() throws Exception { void requestPartsSnippet() throws Exception {
MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.context) MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.context)
.apply(documentationConfiguration(this.restDocumentation)) .apply(documentationConfiguration(this.restDocumentation))
.build(); .build();
@@ -341,7 +354,7 @@ public class MockMvcRestDocumentationIntegrationTests {
} }
@Test @Test
public void responseFieldsSnippet() throws Exception { void responseFieldsSnippet() throws Exception {
MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.context) MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.context)
.apply(documentationConfiguration(this.restDocumentation)) .apply(documentationConfiguration(this.restDocumentation))
.build(); .build();
@@ -354,7 +367,7 @@ public class MockMvcRestDocumentationIntegrationTests {
} }
@Test @Test
public void responseWithSetCookie() throws Exception { void responseWithSetCookie() throws Exception {
MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.context) MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.context)
.apply(documentationConfiguration(this.restDocumentation)) .apply(documentationConfiguration(this.restDocumentation))
.build(); .build();
@@ -368,7 +381,7 @@ public class MockMvcRestDocumentationIntegrationTests {
} }
@Test @Test
public void parameterizedOutputDirectory() throws Exception { void parameterizedOutputDirectory() throws Exception {
MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.context) MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.context)
.apply(documentationConfiguration(this.restDocumentation)) .apply(documentationConfiguration(this.restDocumentation))
.build(); .build();
@@ -380,7 +393,7 @@ public class MockMvcRestDocumentationIntegrationTests {
} }
@Test @Test
public void multiStep() throws Exception { void multiStep() throws Exception {
MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.context) MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.context)
.apply(documentationConfiguration(this.restDocumentation)) .apply(documentationConfiguration(this.restDocumentation))
.alwaysDo(document("{method-name}-{step}")) .alwaysDo(document("{method-name}-{step}"))
@@ -398,7 +411,7 @@ public class MockMvcRestDocumentationIntegrationTests {
} }
@Test @Test
public void alwaysDoWithAdditionalSnippets() throws Exception { void alwaysDoWithAdditionalSnippets() throws Exception {
RestDocumentationResultHandler documentation = document("{method-name}-{step}"); RestDocumentationResultHandler documentation = document("{method-name}-{step}");
MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.context) MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.context)
.apply(documentationConfiguration(this.restDocumentation)) .apply(documentationConfiguration(this.restDocumentation))
@@ -412,7 +425,7 @@ public class MockMvcRestDocumentationIntegrationTests {
} }
@Test @Test
public void preprocessedRequest() throws Exception { void preprocessedRequest() throws Exception {
MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.context) MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.context)
.apply(documentationConfiguration(this.restDocumentation)) .apply(documentationConfiguration(this.restDocumentation))
.build(); .build();
@@ -455,7 +468,7 @@ public class MockMvcRestDocumentationIntegrationTests {
} }
@Test @Test
public void defaultPreprocessedRequest() throws Exception { void defaultPreprocessedRequest() throws Exception {
Pattern pattern = Pattern.compile("(\"alpha\")"); Pattern pattern = Pattern.compile("(\"alpha\")");
MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.context) MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.context)
.apply(documentationConfiguration(this.restDocumentation).operationPreprocessors() .apply(documentationConfiguration(this.restDocumentation).operationPreprocessors()
@@ -486,7 +499,7 @@ public class MockMvcRestDocumentationIntegrationTests {
} }
@Test @Test
public void preprocessedResponse() throws Exception { void preprocessedResponse() throws Exception {
MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.context) MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.context)
.apply(documentationConfiguration(this.restDocumentation)) .apply(documentationConfiguration(this.restDocumentation))
.build(); .build();
@@ -515,7 +528,7 @@ public class MockMvcRestDocumentationIntegrationTests {
} }
@Test @Test
public void defaultPreprocessedResponse() throws Exception { void defaultPreprocessedResponse() throws Exception {
Pattern pattern = Pattern.compile("(\"alpha\")"); Pattern pattern = Pattern.compile("(\"alpha\")");
MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.context) MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.context)
.apply(documentationConfiguration(this.restDocumentation).operationPreprocessors() .apply(documentationConfiguration(this.restDocumentation).operationPreprocessors()
@@ -537,7 +550,7 @@ public class MockMvcRestDocumentationIntegrationTests {
} }
@Test @Test
public void customSnippetTemplate() throws Exception { void customSnippetTemplate() throws Exception {
MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.context) MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.context)
.apply(documentationConfiguration(this.restDocumentation)) .apply(documentationConfiguration(this.restDocumentation))
.build(); .build();
@@ -559,7 +572,7 @@ public class MockMvcRestDocumentationIntegrationTests {
} }
@Test @Test
public void customContextPath() throws Exception { void customContextPath() throws Exception {
MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.context) MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.context)
.apply(documentationConfiguration(this.restDocumentation)) .apply(documentationConfiguration(this.restDocumentation))
.build(); .build();
@@ -573,7 +586,7 @@ public class MockMvcRestDocumentationIntegrationTests {
} }
@Test @Test
public void exceptionShouldBeThrownWhenCallDocumentMockMvcNotConfigured() { void exceptionShouldBeThrownWhenCallDocumentMockMvcNotConfigured() {
MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.context).build(); MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.context).build();
assertThatThrownBy(() -> mockMvc.perform(get("/").accept(MediaType.APPLICATION_JSON)).andDo(document("basic"))) assertThatThrownBy(() -> mockMvc.perform(get("/").accept(MediaType.APPLICATION_JSON)).andDo(document("basic")))
.isInstanceOf(IllegalStateException.class) .isInstanceOf(IllegalStateException.class)
@@ -583,7 +596,7 @@ public class MockMvcRestDocumentationIntegrationTests {
} }
@Test @Test
public void exceptionShouldBeThrownWhenCallDocumentSnippetsMockMvcNotConfigured() { void exceptionShouldBeThrownWhenCallDocumentSnippetsMockMvcNotConfigured() {
RestDocumentationResultHandler documentation = document("{method-name}-{step}"); RestDocumentationResultHandler documentation = document("{method-name}-{step}");
MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.context).build(); MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.context).build();
assertThatThrownBy(() -> mockMvc.perform(get("/").accept(MediaType.APPLICATION_JSON)) assertThatThrownBy(() -> mockMvc.perform(get("/").accept(MediaType.APPLICATION_JSON))
@@ -594,7 +607,7 @@ public class MockMvcRestDocumentationIntegrationTests {
} }
@Test @Test
public void multiPart() throws Exception { void multiPart() throws Exception {
MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.context) MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.context)
.apply(documentationConfiguration(this.restDocumentation)) .apply(documentationConfiguration(this.restDocumentation))
.build(); .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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with 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 java.net.URI;
import jakarta.servlet.ServletContext; import jakarta.servlet.ServletContext;
import org.junit.Test; import org.junit.jupiter.api.Test;
import org.springframework.http.HttpMethod; import org.springframework.http.HttpMethod;
import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpServletRequest;
@@ -44,97 +44,97 @@ import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuild
* @author Andy Wilkinson * @author Andy Wilkinson
* *
*/ */
public class RestDocumentationRequestBuildersTests { class RestDocumentationRequestBuildersTests {
private final ServletContext servletContext = new MockServletContext(); private final ServletContext servletContext = new MockServletContext();
@Test @Test
public void getTemplate() { void getTemplate() {
assertTemplate(get("/{template}", "t"), HttpMethod.GET); assertTemplate(get("/{template}", "t"), HttpMethod.GET);
} }
@Test @Test
public void getUri() { void getUri() {
assertUri(get(URI.create("/uri")), HttpMethod.GET); assertUri(get(URI.create("/uri")), HttpMethod.GET);
} }
@Test @Test
public void postTemplate() { void postTemplate() {
assertTemplate(post("/{template}", "t"), HttpMethod.POST); assertTemplate(post("/{template}", "t"), HttpMethod.POST);
} }
@Test @Test
public void postUri() { void postUri() {
assertUri(post(URI.create("/uri")), HttpMethod.POST); assertUri(post(URI.create("/uri")), HttpMethod.POST);
} }
@Test @Test
public void putTemplate() { void putTemplate() {
assertTemplate(put("/{template}", "t"), HttpMethod.PUT); assertTemplate(put("/{template}", "t"), HttpMethod.PUT);
} }
@Test @Test
public void putUri() { void putUri() {
assertUri(put(URI.create("/uri")), HttpMethod.PUT); assertUri(put(URI.create("/uri")), HttpMethod.PUT);
} }
@Test @Test
public void patchTemplate() { void patchTemplate() {
assertTemplate(patch("/{template}", "t"), HttpMethod.PATCH); assertTemplate(patch("/{template}", "t"), HttpMethod.PATCH);
} }
@Test @Test
public void patchUri() { void patchUri() {
assertUri(patch(URI.create("/uri")), HttpMethod.PATCH); assertUri(patch(URI.create("/uri")), HttpMethod.PATCH);
} }
@Test @Test
public void deleteTemplate() { void deleteTemplate() {
assertTemplate(delete("/{template}", "t"), HttpMethod.DELETE); assertTemplate(delete("/{template}", "t"), HttpMethod.DELETE);
} }
@Test @Test
public void deleteUri() { void deleteUri() {
assertUri(delete(URI.create("/uri")), HttpMethod.DELETE); assertUri(delete(URI.create("/uri")), HttpMethod.DELETE);
} }
@Test @Test
public void optionsTemplate() { void optionsTemplate() {
assertTemplate(options("/{template}", "t"), HttpMethod.OPTIONS); assertTemplate(options("/{template}", "t"), HttpMethod.OPTIONS);
} }
@Test @Test
public void optionsUri() { void optionsUri() {
assertUri(options(URI.create("/uri")), HttpMethod.OPTIONS); assertUri(options(URI.create("/uri")), HttpMethod.OPTIONS);
} }
@Test @Test
public void headTemplate() { void headTemplate() {
assertTemplate(head("/{template}", "t"), HttpMethod.HEAD); assertTemplate(head("/{template}", "t"), HttpMethod.HEAD);
} }
@Test @Test
public void headUri() { void headUri() {
assertUri(head(URI.create("/uri")), HttpMethod.HEAD); assertUri(head(URI.create("/uri")), HttpMethod.HEAD);
} }
@Test @Test
public void requestTemplate() { void requestTemplate() {
assertTemplate(request(HttpMethod.GET, "/{template}", "t"), HttpMethod.GET); assertTemplate(request(HttpMethod.GET, "/{template}", "t"), HttpMethod.GET);
} }
@Test @Test
public void requestUri() { void requestUri() {
assertUri(request(HttpMethod.GET, URI.create("/uri")), HttpMethod.GET); assertUri(request(HttpMethod.GET, URI.create("/uri")), HttpMethod.GET);
} }
@Test @Test
public void multipartTemplate() { void multipartTemplate() {
assertTemplate(multipart("/{template}", "t"), HttpMethod.POST); assertTemplate(multipart("/{template}", "t"), HttpMethod.POST);
} }
@Test @Test
public void multipartUri() { void multipartUri() {
assertUri(multipart(URI.create("/uri")), HttpMethod.POST); 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.pdfbox:pdfbox:2.0.27")
api("org.apache.tomcat.embed:tomcat-embed-core:11.0.2") api("org.apache.tomcat.embed:tomcat-embed-core:11.0.2")
api("org.apache.tomcat.embed:tomcat-embed-el: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:3.0.0")
api("org.asciidoctor:asciidoctorj-pdf:2.3.19") api("org.asciidoctor:asciidoctorj-pdf:2.3.19")
api("org.assertj:assertj-core:3.23.1") api("org.assertj:assertj-core:3.23.1")
@@ -22,10 +23,10 @@ dependencies {
api("org.hamcrest:hamcrest-library:1.3") api("org.hamcrest:hamcrest-library:1.3")
api("org.hibernate.validator:hibernate-validator:9.0.0.CR1") api("org.hibernate.validator:hibernate-validator:9.0.0.CR1")
api("org.javamoney:moneta:1.4.2") 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("com.fasterxml.jackson:jackson-bom:2.14.0"))
api(enforcedPlatform("io.rest-assured:rest-assured-bom:5.2.1")) api(enforcedPlatform("io.rest-assured:rest-assured-bom:5.2.1"))
api(enforcedPlatform("org.mockito:mockito-bom:4.9.0")) 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")) api(enforcedPlatform("org.springframework:spring-framework-bom:$springFrameworkVersion"))
} }

View File

@@ -13,13 +13,14 @@ dependencies {
internal(platform(project(":spring-restdocs-platform"))) internal(platform(project(":spring-restdocs-platform")))
testCompileOnly("org.apiguardian:apiguardian-api")
testImplementation(testFixtures(project(":spring-restdocs-core"))) testImplementation(testFixtures(project(":spring-restdocs-core")))
testImplementation("com.fasterxml.jackson.core:jackson-databind") testImplementation("com.fasterxml.jackson.core:jackson-databind")
testImplementation("junit:junit")
testImplementation("org.apache.tomcat.embed:tomcat-embed-core") 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 { 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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with 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.RestAssured;
import io.restassured.specification.RequestSpecification; import io.restassured.specification.RequestSpecification;
import org.assertj.core.api.AbstractAssert; import org.assertj.core.api.AbstractAssert;
import org.junit.ClassRule; import org.junit.jupiter.api.Test;
import org.junit.Test; import org.junit.jupiter.api.extension.RegisterExtension;
import org.springframework.http.HttpMethod; import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
@@ -34,12 +34,12 @@ import static org.assertj.core.api.Assertions.assertThat;
* *
* @author Andy Wilkinson * @author Andy Wilkinson
*/ */
public class RestAssuredParameterBehaviorTests { class RestAssuredParameterBehaviorTests {
private static final MediaType APPLICATION_FORM_URLENCODED_ISO_8859_1 = MediaType private static final MediaType APPLICATION_FORM_URLENCODED_ISO_8859_1 = MediaType
.parseMediaType(MediaType.APPLICATION_FORM_URLENCODED_VALUE + ";charset=ISO-8859-1"); .parseMediaType(MediaType.APPLICATION_FORM_URLENCODED_VALUE + ";charset=ISO-8859-1");
@ClassRule @RegisterExtension
public static TomcatServer tomcat = new TomcatServer(); public static TomcatServer tomcat = new TomcatServer();
private final RestAssuredRequestConverter factory = new RestAssuredRequestConverter(); private final RestAssuredRequestConverter factory = new RestAssuredRequestConverter();
@@ -54,7 +54,7 @@ public class RestAssuredParameterBehaviorTests {
}); });
@Test @Test
public void queryParameterOnGet() { void queryParameterOnGet() {
this.spec.queryParam("a", "alpha", "apple") this.spec.queryParam("a", "alpha", "apple")
.queryParam("b", "bravo") .queryParam("b", "bravo")
.get("/query-parameter") .get("/query-parameter")
@@ -64,7 +64,7 @@ public class RestAssuredParameterBehaviorTests {
} }
@Test @Test
public void queryParameterOnHead() { void queryParameterOnHead() {
this.spec.queryParam("a", "alpha", "apple") this.spec.queryParam("a", "alpha", "apple")
.queryParam("b", "bravo") .queryParam("b", "bravo")
.head("/query-parameter") .head("/query-parameter")
@@ -74,7 +74,7 @@ public class RestAssuredParameterBehaviorTests {
} }
@Test @Test
public void queryParameterOnPost() { void queryParameterOnPost() {
this.spec.queryParam("a", "alpha", "apple") this.spec.queryParam("a", "alpha", "apple")
.queryParam("b", "bravo") .queryParam("b", "bravo")
.post("/query-parameter") .post("/query-parameter")
@@ -84,7 +84,7 @@ public class RestAssuredParameterBehaviorTests {
} }
@Test @Test
public void queryParameterOnPut() { void queryParameterOnPut() {
this.spec.queryParam("a", "alpha", "apple") this.spec.queryParam("a", "alpha", "apple")
.queryParam("b", "bravo") .queryParam("b", "bravo")
.put("/query-parameter") .put("/query-parameter")
@@ -94,7 +94,7 @@ public class RestAssuredParameterBehaviorTests {
} }
@Test @Test
public void queryParameterOnPatch() { void queryParameterOnPatch() {
this.spec.queryParam("a", "alpha", "apple") this.spec.queryParam("a", "alpha", "apple")
.queryParam("b", "bravo") .queryParam("b", "bravo")
.patch("/query-parameter") .patch("/query-parameter")
@@ -104,7 +104,7 @@ public class RestAssuredParameterBehaviorTests {
} }
@Test @Test
public void queryParameterOnDelete() { void queryParameterOnDelete() {
this.spec.queryParam("a", "alpha", "apple") this.spec.queryParam("a", "alpha", "apple")
.queryParam("b", "bravo") .queryParam("b", "bravo")
.delete("/query-parameter") .delete("/query-parameter")
@@ -114,7 +114,7 @@ public class RestAssuredParameterBehaviorTests {
} }
@Test @Test
public void queryParameterOnOptions() { void queryParameterOnOptions() {
this.spec.queryParam("a", "alpha", "apple") this.spec.queryParam("a", "alpha", "apple")
.queryParam("b", "bravo") .queryParam("b", "bravo")
.options("/query-parameter") .options("/query-parameter")
@@ -124,49 +124,49 @@ public class RestAssuredParameterBehaviorTests {
} }
@Test @Test
public void paramOnGet() { void paramOnGet() {
this.spec.param("a", "alpha", "apple").param("b", "bravo").get("/query-parameter").then().statusCode(200); this.spec.param("a", "alpha", "apple").param("b", "bravo").get("/query-parameter").then().statusCode(200);
assertThatRequest(this.request).hasQueryParametersWithMethod(HttpMethod.GET); assertThatRequest(this.request).hasQueryParametersWithMethod(HttpMethod.GET);
} }
@Test @Test
public void paramOnHead() { void paramOnHead() {
this.spec.param("a", "alpha", "apple").param("b", "bravo").head("/query-parameter").then().statusCode(200); this.spec.param("a", "alpha", "apple").param("b", "bravo").head("/query-parameter").then().statusCode(200);
assertThatRequest(this.request).hasQueryParametersWithMethod(HttpMethod.HEAD); assertThatRequest(this.request).hasQueryParametersWithMethod(HttpMethod.HEAD);
} }
@Test @Test
public void paramOnPost() { void paramOnPost() {
this.spec.param("a", "alpha", "apple").param("b", "bravo").post("/form-url-encoded").then().statusCode(200); this.spec.param("a", "alpha", "apple").param("b", "bravo").post("/form-url-encoded").then().statusCode(200);
assertThatRequest(this.request).isFormUrlEncodedWithMethod(HttpMethod.POST); assertThatRequest(this.request).isFormUrlEncodedWithMethod(HttpMethod.POST);
} }
@Test @Test
public void paramOnPut() { void paramOnPut() {
this.spec.param("a", "alpha", "apple").param("b", "bravo").put("/query-parameter").then().statusCode(200); this.spec.param("a", "alpha", "apple").param("b", "bravo").put("/query-parameter").then().statusCode(200);
assertThatRequest(this.request).hasQueryParametersWithMethod(HttpMethod.PUT); assertThatRequest(this.request).hasQueryParametersWithMethod(HttpMethod.PUT);
} }
@Test @Test
public void paramOnPatch() { void paramOnPatch() {
this.spec.param("a", "alpha", "apple").param("b", "bravo").patch("/query-parameter").then().statusCode(200); this.spec.param("a", "alpha", "apple").param("b", "bravo").patch("/query-parameter").then().statusCode(200);
assertThatRequest(this.request).hasQueryParametersWithMethod(HttpMethod.PATCH); assertThatRequest(this.request).hasQueryParametersWithMethod(HttpMethod.PATCH);
} }
@Test @Test
public void paramOnDelete() { void paramOnDelete() {
this.spec.param("a", "alpha", "apple").param("b", "bravo").delete("/query-parameter").then().statusCode(200); this.spec.param("a", "alpha", "apple").param("b", "bravo").delete("/query-parameter").then().statusCode(200);
assertThatRequest(this.request).hasQueryParametersWithMethod(HttpMethod.DELETE); assertThatRequest(this.request).hasQueryParametersWithMethod(HttpMethod.DELETE);
} }
@Test @Test
public void paramOnOptions() { void paramOnOptions() {
this.spec.param("a", "alpha", "apple").param("b", "bravo").options("/query-parameter").then().statusCode(200); this.spec.param("a", "alpha", "apple").param("b", "bravo").options("/query-parameter").then().statusCode(200);
assertThatRequest(this.request).hasQueryParametersWithMethod(HttpMethod.OPTIONS); assertThatRequest(this.request).hasQueryParametersWithMethod(HttpMethod.OPTIONS);
} }
@Test @Test
public void formParamOnGet() { void formParamOnGet() {
this.spec.formParam("a", "alpha", "apple") this.spec.formParam("a", "alpha", "apple")
.formParam("b", "bravo") .formParam("b", "bravo")
.get("/query-parameter") .get("/query-parameter")
@@ -176,7 +176,7 @@ public class RestAssuredParameterBehaviorTests {
} }
@Test @Test
public void formParamOnHead() { void formParamOnHead() {
this.spec.formParam("a", "alpha", "apple") this.spec.formParam("a", "alpha", "apple")
.formParam("b", "bravo") .formParam("b", "bravo")
.head("/form-url-encoded") .head("/form-url-encoded")
@@ -186,7 +186,7 @@ public class RestAssuredParameterBehaviorTests {
} }
@Test @Test
public void formParamOnPost() { void formParamOnPost() {
this.spec.formParam("a", "alpha", "apple") this.spec.formParam("a", "alpha", "apple")
.formParam("b", "bravo") .formParam("b", "bravo")
.post("/form-url-encoded") .post("/form-url-encoded")
@@ -196,7 +196,7 @@ public class RestAssuredParameterBehaviorTests {
} }
@Test @Test
public void formParamOnPut() { void formParamOnPut() {
this.spec.formParam("a", "alpha", "apple") this.spec.formParam("a", "alpha", "apple")
.formParam("b", "bravo") .formParam("b", "bravo")
.put("/form-url-encoded") .put("/form-url-encoded")
@@ -206,7 +206,7 @@ public class RestAssuredParameterBehaviorTests {
} }
@Test @Test
public void formParamOnPatch() { void formParamOnPatch() {
this.spec.formParam("a", "alpha", "apple") this.spec.formParam("a", "alpha", "apple")
.formParam("b", "bravo") .formParam("b", "bravo")
.patch("/form-url-encoded") .patch("/form-url-encoded")
@@ -216,7 +216,7 @@ public class RestAssuredParameterBehaviorTests {
} }
@Test @Test
public void formParamOnDelete() { void formParamOnDelete() {
this.spec.formParam("a", "alpha", "apple") this.spec.formParam("a", "alpha", "apple")
.formParam("b", "bravo") .formParam("b", "bravo")
.delete("/form-url-encoded") .delete("/form-url-encoded")
@@ -226,7 +226,7 @@ public class RestAssuredParameterBehaviorTests {
} }
@Test @Test
public void formParamOnOptions() { void formParamOnOptions() {
this.spec.formParam("a", "alpha", "apple") this.spec.formParam("a", "alpha", "apple")
.formParam("b", "bravo") .formParam("b", "bravo")
.options("/form-url-encoded") .options("/form-url-encoded")

View File

@@ -28,8 +28,8 @@ import java.util.Iterator;
import io.restassured.RestAssured; import io.restassured.RestAssured;
import io.restassured.specification.FilterableRequestSpecification; import io.restassured.specification.FilterableRequestSpecification;
import io.restassured.specification.RequestSpecification; import io.restassured.specification.RequestSpecification;
import org.junit.ClassRule; import org.junit.jupiter.api.Test;
import org.junit.Test; import org.junit.jupiter.api.extension.RegisterExtension;
import org.springframework.http.HttpHeaders; import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod; import org.springframework.http.HttpMethod;
@@ -47,15 +47,15 @@ import static org.assertj.core.api.Assertions.entry;
* *
* @author Andy Wilkinson * @author Andy Wilkinson
*/ */
public class RestAssuredRequestConverterTests { class RestAssuredRequestConverterTests {
@ClassRule @RegisterExtension
public static TomcatServer tomcat = new TomcatServer(); public static TomcatServer tomcat = new TomcatServer();
private final RestAssuredRequestConverter factory = new RestAssuredRequestConverter(); private final RestAssuredRequestConverter factory = new RestAssuredRequestConverter();
@Test @Test
public void requestUri() { void requestUri() {
RequestSpecification requestSpec = RestAssured.given().port(tomcat.getPort()); RequestSpecification requestSpec = RestAssured.given().port(tomcat.getPort());
requestSpec.get("/foo/bar"); requestSpec.get("/foo/bar");
OperationRequest request = this.factory.convert((FilterableRequestSpecification) requestSpec); OperationRequest request = this.factory.convert((FilterableRequestSpecification) requestSpec);
@@ -63,7 +63,7 @@ public class RestAssuredRequestConverterTests {
} }
@Test @Test
public void requestMethod() { void requestMethod() {
RequestSpecification requestSpec = RestAssured.given().port(tomcat.getPort()); RequestSpecification requestSpec = RestAssured.given().port(tomcat.getPort());
requestSpec.head("/foo/bar"); requestSpec.head("/foo/bar");
OperationRequest request = this.factory.convert((FilterableRequestSpecification) requestSpec); OperationRequest request = this.factory.convert((FilterableRequestSpecification) requestSpec);
@@ -71,7 +71,7 @@ public class RestAssuredRequestConverterTests {
} }
@Test @Test
public void queryStringParameters() { void queryStringParameters() {
RequestSpecification requestSpec = RestAssured.given().port(tomcat.getPort()).queryParam("foo", "bar"); RequestSpecification requestSpec = RestAssured.given().port(tomcat.getPort()).queryParam("foo", "bar");
requestSpec.get("/"); requestSpec.get("/");
OperationRequest request = this.factory.convert((FilterableRequestSpecification) requestSpec); OperationRequest request = this.factory.convert((FilterableRequestSpecification) requestSpec);
@@ -79,7 +79,7 @@ public class RestAssuredRequestConverterTests {
} }
@Test @Test
public void queryStringFromUrlParameters() { void queryStringFromUrlParameters() {
RequestSpecification requestSpec = RestAssured.given().port(tomcat.getPort()); RequestSpecification requestSpec = RestAssured.given().port(tomcat.getPort());
requestSpec.get("/?foo=bar&foo=qix"); requestSpec.get("/?foo=bar&foo=qix");
OperationRequest request = this.factory.convert((FilterableRequestSpecification) requestSpec); OperationRequest request = this.factory.convert((FilterableRequestSpecification) requestSpec);
@@ -87,7 +87,7 @@ public class RestAssuredRequestConverterTests {
} }
@Test @Test
public void paramOnGetRequestIsMappedToQueryString() { void paramOnGetRequestIsMappedToQueryString() {
RequestSpecification requestSpec = RestAssured.given().port(tomcat.getPort()).param("foo", "bar"); RequestSpecification requestSpec = RestAssured.given().port(tomcat.getPort()).param("foo", "bar");
requestSpec.get("/"); requestSpec.get("/");
OperationRequest request = this.factory.convert((FilterableRequestSpecification) requestSpec); OperationRequest request = this.factory.convert((FilterableRequestSpecification) requestSpec);
@@ -95,7 +95,7 @@ public class RestAssuredRequestConverterTests {
} }
@Test @Test
public void headers() { void headers() {
RequestSpecification requestSpec = RestAssured.given().port(tomcat.getPort()).header("Foo", "bar"); RequestSpecification requestSpec = RestAssured.given().port(tomcat.getPort()).header("Foo", "bar");
requestSpec.get("/"); requestSpec.get("/");
OperationRequest request = this.factory.convert((FilterableRequestSpecification) requestSpec); OperationRequest request = this.factory.convert((FilterableRequestSpecification) requestSpec);
@@ -104,7 +104,7 @@ public class RestAssuredRequestConverterTests {
} }
@Test @Test
public void headersWithCustomAccept() { void headersWithCustomAccept() {
RequestSpecification requestSpec = RestAssured.given() RequestSpecification requestSpec = RestAssured.given()
.port(tomcat.getPort()) .port(tomcat.getPort())
.header("Foo", "bar") .header("Foo", "bar")
@@ -117,7 +117,7 @@ public class RestAssuredRequestConverterTests {
} }
@Test @Test
public void cookies() { void cookies() {
RequestSpecification requestSpec = RestAssured.given() RequestSpecification requestSpec = RestAssured.given()
.port(tomcat.getPort()) .port(tomcat.getPort())
.cookie("cookie1", "cookieVal1") .cookie("cookie1", "cookieVal1")
@@ -138,7 +138,7 @@ public class RestAssuredRequestConverterTests {
} }
@Test @Test
public void multipart() { void multipart() {
RequestSpecification requestSpec = RestAssured.given() RequestSpecification requestSpec = RestAssured.given()
.port(tomcat.getPort()) .port(tomcat.getPort())
.multiPart("a", "a.txt", "alpha", null) .multiPart("a", "a.txt", "alpha", null)
@@ -156,14 +156,14 @@ public class RestAssuredRequestConverterTests {
} }
@Test @Test
public void byteArrayBody() { void byteArrayBody() {
RequestSpecification requestSpec = RestAssured.given().body("body".getBytes()).port(tomcat.getPort()); RequestSpecification requestSpec = RestAssured.given().body("body".getBytes()).port(tomcat.getPort());
requestSpec.post(); requestSpec.post();
this.factory.convert((FilterableRequestSpecification) requestSpec); this.factory.convert((FilterableRequestSpecification) requestSpec);
} }
@Test @Test
public void stringBody() { void stringBody() {
RequestSpecification requestSpec = RestAssured.given().body("body").port(tomcat.getPort()); RequestSpecification requestSpec = RestAssured.given().body("body").port(tomcat.getPort());
requestSpec.post(); requestSpec.post();
OperationRequest request = this.factory.convert((FilterableRequestSpecification) requestSpec); OperationRequest request = this.factory.convert((FilterableRequestSpecification) requestSpec);
@@ -171,7 +171,7 @@ public class RestAssuredRequestConverterTests {
} }
@Test @Test
public void objectBody() { void objectBody() {
RequestSpecification requestSpec = RestAssured.given().body(new ObjectBody("bar")).port(tomcat.getPort()); RequestSpecification requestSpec = RestAssured.given().body(new ObjectBody("bar")).port(tomcat.getPort());
requestSpec.post(); requestSpec.post();
OperationRequest request = this.factory.convert((FilterableRequestSpecification) requestSpec); OperationRequest request = this.factory.convert((FilterableRequestSpecification) requestSpec);
@@ -179,7 +179,7 @@ public class RestAssuredRequestConverterTests {
} }
@Test @Test
public void byteArrayInputStreamBody() { void byteArrayInputStreamBody() {
RequestSpecification requestSpec = RestAssured.given() RequestSpecification requestSpec = RestAssured.given()
.body(new ByteArrayInputStream(new byte[] { 1, 2, 3, 4 })) .body(new ByteArrayInputStream(new byte[] { 1, 2, 3, 4 }))
.port(tomcat.getPort()); .port(tomcat.getPort());
@@ -189,7 +189,7 @@ public class RestAssuredRequestConverterTests {
} }
@Test @Test
public void fileBody() { void fileBody() {
RequestSpecification requestSpec = RestAssured.given() RequestSpecification requestSpec = RestAssured.given()
.body(new File("src/test/resources/body.txt")) .body(new File("src/test/resources/body.txt"))
.port(tomcat.getPort()); .port(tomcat.getPort());
@@ -199,7 +199,7 @@ public class RestAssuredRequestConverterTests {
} }
@Test @Test
public void fileInputStreamBody() throws FileNotFoundException { void fileInputStreamBody() throws FileNotFoundException {
FileInputStream inputStream = new FileInputStream("src/test/resources/body.txt"); FileInputStream inputStream = new FileInputStream("src/test/resources/body.txt");
RequestSpecification requestSpec = RestAssured.given().body(inputStream).port(tomcat.getPort()); RequestSpecification requestSpec = RestAssured.given().body(inputStream).port(tomcat.getPort());
requestSpec.post(); requestSpec.post();
@@ -209,7 +209,7 @@ public class RestAssuredRequestConverterTests {
} }
@Test @Test
public void multipartWithByteArrayInputStreamBody() { void multipartWithByteArrayInputStreamBody() {
RequestSpecification requestSpec = RestAssured.given() RequestSpecification requestSpec = RestAssured.given()
.port(tomcat.getPort()) .port(tomcat.getPort())
.multiPart("foo", "foo.txt", new ByteArrayInputStream("foo".getBytes())); .multiPart("foo", "foo.txt", new ByteArrayInputStream("foo".getBytes()));
@@ -219,7 +219,7 @@ public class RestAssuredRequestConverterTests {
} }
@Test @Test
public void multipartWithStringBody() { void multipartWithStringBody() {
RequestSpecification requestSpec = RestAssured.given().port(tomcat.getPort()).multiPart("control", "foo"); RequestSpecification requestSpec = RestAssured.given().port(tomcat.getPort()).multiPart("control", "foo");
requestSpec.post(); requestSpec.post();
OperationRequest request = this.factory.convert((FilterableRequestSpecification) requestSpec); OperationRequest request = this.factory.convert((FilterableRequestSpecification) requestSpec);
@@ -227,7 +227,7 @@ public class RestAssuredRequestConverterTests {
} }
@Test @Test
public void multipartWithByteArrayBody() { void multipartWithByteArrayBody() {
RequestSpecification requestSpec = RestAssured.given() RequestSpecification requestSpec = RestAssured.given()
.port(tomcat.getPort()) .port(tomcat.getPort())
.multiPart("control", "file", "foo".getBytes()); .multiPart("control", "file", "foo".getBytes());
@@ -237,7 +237,7 @@ public class RestAssuredRequestConverterTests {
} }
@Test @Test
public void multipartWithFileBody() { void multipartWithFileBody() {
RequestSpecification requestSpec = RestAssured.given() RequestSpecification requestSpec = RestAssured.given()
.port(tomcat.getPort()) .port(tomcat.getPort())
.multiPart(new File("src/test/resources/body.txt")); .multiPart(new File("src/test/resources/body.txt"));
@@ -247,7 +247,7 @@ public class RestAssuredRequestConverterTests {
} }
@Test @Test
public void multipartWithFileInputStreamBody() throws FileNotFoundException { void multipartWithFileInputStreamBody() throws FileNotFoundException {
FileInputStream inputStream = new FileInputStream("src/test/resources/body.txt"); FileInputStream inputStream = new FileInputStream("src/test/resources/body.txt");
RequestSpecification requestSpec = RestAssured.given() RequestSpecification requestSpec = RestAssured.given()
.port(tomcat.getPort()) .port(tomcat.getPort())
@@ -259,7 +259,7 @@ public class RestAssuredRequestConverterTests {
} }
@Test @Test
public void multipartWithObjectBody() { void multipartWithObjectBody() {
RequestSpecification requestSpec = RestAssured.given() RequestSpecification requestSpec = RestAssured.given()
.port(tomcat.getPort()) .port(tomcat.getPort())
.multiPart("control", new ObjectBody("bar")); .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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with 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.http.Headers;
import io.restassured.response.Response; import io.restassured.response.Response;
import io.restassured.response.ResponseBody; import io.restassured.response.ResponseBody;
import org.junit.Test; import org.junit.jupiter.api.Test;
import org.springframework.http.HttpStatusCode; import org.springframework.http.HttpStatusCode;
import org.springframework.restdocs.operation.OperationResponse; import org.springframework.restdocs.operation.OperationResponse;
@@ -33,12 +33,12 @@ import static org.mockito.Mockito.mock;
* *
* @author Andy Wilkinson * @author Andy Wilkinson
*/ */
public class RestAssuredResponseConverterTests { class RestAssuredResponseConverterTests {
private final RestAssuredResponseConverter converter = new RestAssuredResponseConverter(); private final RestAssuredResponseConverter converter = new RestAssuredResponseConverter();
@Test @Test
public void responseWithCustomStatus() { void responseWithCustomStatus() {
Response response = mock(Response.class); Response response = mock(Response.class);
given(response.getStatusCode()).willReturn(600); given(response.getStatusCode()).willReturn(600);
given(response.getHeaders()).willReturn(new Headers()); 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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with 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.filter.FilterContext;
import io.restassured.specification.FilterableRequestSpecification; import io.restassured.specification.FilterableRequestSpecification;
import io.restassured.specification.FilterableResponseSpecification; import io.restassured.specification.FilterableResponseSpecification;
import org.junit.Rule; import org.junit.jupiter.api.BeforeEach;
import org.junit.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.ArgumentCaptor; 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.generate.RestDocumentationGenerator;
import org.springframework.restdocs.operation.preprocess.OperationRequestPreprocessor; import org.springframework.restdocs.operation.preprocess.OperationRequestPreprocessor;
import org.springframework.restdocs.operation.preprocess.OperationResponsePreprocessor; import org.springframework.restdocs.operation.preprocess.OperationResponsePreprocessor;
@@ -45,10 +47,8 @@ import static org.mockito.Mockito.verify;
* @author Andy Wilkinson * @author Andy Wilkinson
* @author Filip Hrisafov * @author Filip Hrisafov
*/ */
public class RestAssuredRestDocumentationConfigurerTests { @ExtendWith(RestDocumentationExtension.class)
class RestAssuredRestDocumentationConfigurerTests {
@Rule
public final JUnitRestDocumentation restDocumentation = new JUnitRestDocumentation();
private final FilterableRequestSpecification requestSpec = mock(FilterableRequestSpecification.class); private final FilterableRequestSpecification requestSpec = mock(FilterableRequestSpecification.class);
@@ -56,17 +56,21 @@ public class RestAssuredRestDocumentationConfigurerTests {
private final FilterContext filterContext = mock(FilterContext.class); private final FilterContext filterContext = mock(FilterContext.class);
private final RestAssuredRestDocumentationConfigurer configurer = new RestAssuredRestDocumentationConfigurer( private RestAssuredRestDocumentationConfigurer configurer;
this.restDocumentation);
@BeforeEach
void setUp(RestDocumentationContextProvider restDocumentation) {
this.configurer = new RestAssuredRestDocumentationConfigurer(restDocumentation);
}
@Test @Test
public void nextFilterIsCalled() { void nextFilterIsCalled() {
this.configurer.filter(this.requestSpec, this.responseSpec, this.filterContext); this.configurer.filter(this.requestSpec, this.responseSpec, this.filterContext);
verify(this.filterContext).next(this.requestSpec, this.responseSpec); verify(this.filterContext).next(this.requestSpec, this.responseSpec);
} }
@Test @Test
public void configurationIsAddedToTheContext() { void configurationIsAddedToTheContext() {
this.configurer.operationPreprocessors() this.configurer.operationPreprocessors()
.withRequestDefaults(Preprocessors.prettyPrint()) .withRequestDefaults(Preprocessors.prettyPrint())
.withResponseDefaults(Preprocessors.modifyHeaders().remove("Foo")) .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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with 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.builder.RequestSpecBuilder;
import io.restassured.specification.RequestSpecification; import io.restassured.specification.RequestSpecification;
import org.assertj.core.api.Condition; import org.assertj.core.api.Condition;
import org.junit.ClassRule; import org.junit.jupiter.api.Test;
import org.junit.Rule; import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.Test; import org.junit.jupiter.api.extension.RegisterExtension;
import org.springframework.http.HttpHeaders; import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType; 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.TemplateFormat;
import org.springframework.restdocs.templates.TemplateFormats; import org.springframework.restdocs.templates.TemplateFormats;
import org.springframework.restdocs.testfixtures.SnippetConditions; import org.springframework.restdocs.testfixtures.SnippetConditions;
@@ -80,18 +81,16 @@ import static org.springframework.restdocs.restassured.RestAssuredRestDocumentat
* @author Tomasz Kopczynski * @author Tomasz Kopczynski
* @author Filip Hrisafov * @author Filip Hrisafov
*/ */
public class RestAssuredRestDocumentationIntegrationTests { @ExtendWith(RestDocumentationExtension.class)
class RestAssuredRestDocumentationIntegrationTests {
@Rule @RegisterExtension
public JUnitRestDocumentation restDocumentation = new JUnitRestDocumentation(); private static TomcatServer tomcat = new TomcatServer();
@ClassRule
public static TomcatServer tomcat = new TomcatServer();
@Test @Test
public void defaultSnippetGeneration() { void defaultSnippetGeneration(RestDocumentationContextProvider restDocumentation) {
given().port(tomcat.getPort()) given().port(tomcat.getPort())
.filter(documentationConfiguration(this.restDocumentation)) .filter(documentationConfiguration(restDocumentation))
.filter(document("default")) .filter(document("default"))
.get("/") .get("/")
.then() .then()
@@ -101,10 +100,10 @@ public class RestAssuredRestDocumentationIntegrationTests {
} }
@Test @Test
public void curlSnippetWithContent() { void curlSnippetWithContent(RestDocumentationContextProvider restDocumentation) {
String contentType = "text/plain; charset=UTF-8"; String contentType = "text/plain; charset=UTF-8";
given().port(tomcat.getPort()) given().port(tomcat.getPort())
.filter(documentationConfiguration(this.restDocumentation)) .filter(documentationConfiguration(restDocumentation))
.filter(document("curl-snippet-with-content")) .filter(document("curl-snippet-with-content"))
.accept("application/json") .accept("application/json")
.body("content") .body("content")
@@ -120,10 +119,10 @@ public class RestAssuredRestDocumentationIntegrationTests {
} }
@Test @Test
public void curlSnippetWithCookies() { void curlSnippetWithCookies(RestDocumentationContextProvider restDocumentation) {
String contentType = "text/plain; charset=UTF-8"; String contentType = "text/plain; charset=UTF-8";
given().port(tomcat.getPort()) given().port(tomcat.getPort())
.filter(documentationConfiguration(this.restDocumentation)) .filter(documentationConfiguration(restDocumentation))
.filter(document("curl-snippet-with-cookies")) .filter(document("curl-snippet-with-cookies"))
.accept("application/json") .accept("application/json")
.contentType(contentType) .contentType(contentType)
@@ -138,9 +137,9 @@ public class RestAssuredRestDocumentationIntegrationTests {
} }
@Test @Test
public void curlSnippetWithEmptyParameterQueryString() { void curlSnippetWithEmptyParameterQueryString(RestDocumentationContextProvider restDocumentation) {
given().port(tomcat.getPort()) given().port(tomcat.getPort())
.filter(documentationConfiguration(this.restDocumentation)) .filter(documentationConfiguration(restDocumentation))
.filter(document("curl-snippet-with-empty-parameter-query-string")) .filter(document("curl-snippet-with-empty-parameter-query-string"))
.accept("application/json") .accept("application/json")
.param("a", "") .param("a", "")
@@ -155,9 +154,9 @@ public class RestAssuredRestDocumentationIntegrationTests {
} }
@Test @Test
public void curlSnippetWithQueryStringOnPost() { void curlSnippetWithQueryStringOnPost(RestDocumentationContextProvider restDocumentation) {
given().port(tomcat.getPort()) given().port(tomcat.getPort())
.filter(documentationConfiguration(this.restDocumentation)) .filter(documentationConfiguration(restDocumentation))
.filter(document("curl-snippet-with-query-string")) .filter(document("curl-snippet-with-query-string"))
.accept("application/json") .accept("application/json")
.param("foo", "bar") .param("foo", "bar")
@@ -174,9 +173,9 @@ public class RestAssuredRestDocumentationIntegrationTests {
} }
@Test @Test
public void linksSnippet() { void linksSnippet(RestDocumentationContextProvider restDocumentation) {
given().port(tomcat.getPort()) given().port(tomcat.getPort())
.filter(documentationConfiguration(this.restDocumentation)) .filter(documentationConfiguration(restDocumentation))
.filter(document("links", links(linkWithRel("rel").description("The description")))) .filter(document("links", links(linkWithRel("rel").description("The description"))))
.accept("application/json") .accept("application/json")
.get("/") .get("/")
@@ -187,9 +186,9 @@ public class RestAssuredRestDocumentationIntegrationTests {
} }
@Test @Test
public void pathParametersSnippet() { void pathParametersSnippet(RestDocumentationContextProvider restDocumentation) {
given().port(tomcat.getPort()) given().port(tomcat.getPort())
.filter(documentationConfiguration(this.restDocumentation)) .filter(documentationConfiguration(restDocumentation))
.filter(document("path-parameters", .filter(document("path-parameters",
pathParameters(parameterWithName("foo").description("The description")))) pathParameters(parameterWithName("foo").description("The description"))))
.accept("application/json") .accept("application/json")
@@ -201,9 +200,9 @@ public class RestAssuredRestDocumentationIntegrationTests {
} }
@Test @Test
public void queryParametersSnippet() { void queryParametersSnippet(RestDocumentationContextProvider restDocumentation) {
given().port(tomcat.getPort()) given().port(tomcat.getPort())
.filter(documentationConfiguration(this.restDocumentation)) .filter(documentationConfiguration(restDocumentation))
.filter(document("query-parameters", .filter(document("query-parameters",
queryParameters(parameterWithName("foo").description("The description")))) queryParameters(parameterWithName("foo").description("The description"))))
.accept("application/json") .accept("application/json")
@@ -216,9 +215,9 @@ public class RestAssuredRestDocumentationIntegrationTests {
} }
@Test @Test
public void requestFieldsSnippet() { void requestFieldsSnippet(RestDocumentationContextProvider restDocumentation) {
given().port(tomcat.getPort()) given().port(tomcat.getPort())
.filter(documentationConfiguration(this.restDocumentation)) .filter(documentationConfiguration(restDocumentation))
.filter(document("request-fields", requestFields(fieldWithPath("a").description("The description")))) .filter(document("request-fields", requestFields(fieldWithPath("a").description("The description"))))
.accept("application/json") .accept("application/json")
.body("{\"a\":\"alpha\"}") .body("{\"a\":\"alpha\"}")
@@ -230,9 +229,9 @@ public class RestAssuredRestDocumentationIntegrationTests {
} }
@Test @Test
public void requestPartsSnippet() { void requestPartsSnippet(RestDocumentationContextProvider restDocumentation) {
given().port(tomcat.getPort()) given().port(tomcat.getPort())
.filter(documentationConfiguration(this.restDocumentation)) .filter(documentationConfiguration(restDocumentation))
.filter(document("request-parts", requestParts(partWithName("a").description("The description")))) .filter(document("request-parts", requestParts(partWithName("a").description("The description"))))
.multiPart("a", "foo") .multiPart("a", "foo")
.post("/upload") .post("/upload")
@@ -243,9 +242,9 @@ public class RestAssuredRestDocumentationIntegrationTests {
} }
@Test @Test
public void responseFieldsSnippet() { void responseFieldsSnippet(RestDocumentationContextProvider restDocumentation) {
given().port(tomcat.getPort()) given().port(tomcat.getPort())
.filter(documentationConfiguration(this.restDocumentation)) .filter(documentationConfiguration(restDocumentation))
.filter(document("response-fields", .filter(document("response-fields",
responseFields(fieldWithPath("a").description("The description"), responseFields(fieldWithPath("a").description("The description"),
subsectionWithPath("links").description("Links to other resources")))) subsectionWithPath("links").description("Links to other resources"))))
@@ -258,9 +257,9 @@ public class RestAssuredRestDocumentationIntegrationTests {
} }
@Test @Test
public void parameterizedOutputDirectory() { void parameterizedOutputDirectory(RestDocumentationContextProvider restDocumentation) {
given().port(tomcat.getPort()) given().port(tomcat.getPort())
.filter(documentationConfiguration(this.restDocumentation)) .filter(documentationConfiguration(restDocumentation))
.filter(document("{method-name}")) .filter(document("{method-name}"))
.get("/") .get("/")
.then() .then()
@@ -270,9 +269,9 @@ public class RestAssuredRestDocumentationIntegrationTests {
} }
@Test @Test
public void multiStep() { void multiStep(RestDocumentationContextProvider restDocumentation) {
RequestSpecification spec = new RequestSpecBuilder().setPort(tomcat.getPort()) RequestSpecification spec = new RequestSpecBuilder().setPort(tomcat.getPort())
.addFilter(documentationConfiguration(this.restDocumentation)) .addFilter(documentationConfiguration(restDocumentation))
.addFilter(document("{method-name}-{step}")) .addFilter(document("{method-name}-{step}"))
.build(); .build();
given(spec).get("/").then().statusCode(200); given(spec).get("/").then().statusCode(200);
@@ -287,10 +286,10 @@ public class RestAssuredRestDocumentationIntegrationTests {
} }
@Test @Test
public void additionalSnippets() { void additionalSnippets(RestDocumentationContextProvider restDocumentation) {
RestDocumentationFilter documentation = document("{method-name}-{step}"); RestDocumentationFilter documentation = document("{method-name}-{step}");
RequestSpecification spec = new RequestSpecBuilder().setPort(tomcat.getPort()) RequestSpecification spec = new RequestSpecBuilder().setPort(tomcat.getPort())
.addFilter(documentationConfiguration(this.restDocumentation)) .addFilter(documentationConfiguration(restDocumentation))
.addFilter(documentation) .addFilter(documentation)
.build(); .build();
given(spec) given(spec)
@@ -304,9 +303,9 @@ public class RestAssuredRestDocumentationIntegrationTests {
} }
@Test @Test
public void responseWithCookie() { void responseWithCookie(RestDocumentationContextProvider restDocumentation) {
given().port(tomcat.getPort()) given().port(tomcat.getPort())
.filter(documentationConfiguration(this.restDocumentation)) .filter(documentationConfiguration(restDocumentation))
.filter(document("set-cookie", .filter(document("set-cookie",
preprocessResponse(modifyHeaders().remove(HttpHeaders.DATE).remove(HttpHeaders.CONTENT_TYPE)))) preprocessResponse(modifyHeaders().remove(HttpHeaders.DATE).remove(HttpHeaders.CONTENT_TYPE))))
.get("/set-cookie") .get("/set-cookie")
@@ -322,10 +321,10 @@ public class RestAssuredRestDocumentationIntegrationTests {
} }
@Test @Test
public void preprocessedRequest() { void preprocessedRequest(RestDocumentationContextProvider restDocumentation) {
Pattern pattern = Pattern.compile("(\"alpha\")"); Pattern pattern = Pattern.compile("(\"alpha\")");
given().port(tomcat.getPort()) given().port(tomcat.getPort())
.filter(documentationConfiguration(this.restDocumentation)) .filter(documentationConfiguration(restDocumentation))
.header("a", "alpha") .header("a", "alpha")
.header("b", "bravo") .header("b", "bravo")
.contentType("application/json") .contentType("application/json")
@@ -356,10 +355,10 @@ public class RestAssuredRestDocumentationIntegrationTests {
} }
@Test @Test
public void defaultPreprocessedRequest() { void defaultPreprocessedRequest(RestDocumentationContextProvider restDocumentation) {
Pattern pattern = Pattern.compile("(\"alpha\")"); Pattern pattern = Pattern.compile("(\"alpha\")");
given().port(tomcat.getPort()) given().port(tomcat.getPort())
.filter(documentationConfiguration(this.restDocumentation).operationPreprocessors() .filter(documentationConfiguration(restDocumentation).operationPreprocessors()
.withRequestDefaults(prettyPrint(), replacePattern(pattern, "\"<<beta>>\""), modifyUris().removePort(), .withRequestDefaults(prettyPrint(), replacePattern(pattern, "\"<<beta>>\""), modifyUris().removePort(),
modifyHeaders().remove("a").remove(HttpHeaders.CONTENT_LENGTH))) modifyHeaders().remove("a").remove(HttpHeaders.CONTENT_LENGTH)))
.header("a", "alpha") .header("a", "alpha")
@@ -381,10 +380,10 @@ public class RestAssuredRestDocumentationIntegrationTests {
} }
@Test @Test
public void preprocessedResponse() { void preprocessedResponse(RestDocumentationContextProvider restDocumentation) {
Pattern pattern = Pattern.compile("(\"alpha\")"); Pattern pattern = Pattern.compile("(\"alpha\")");
given().port(tomcat.getPort()) given().port(tomcat.getPort())
.filter(documentationConfiguration(this.restDocumentation)) .filter(documentationConfiguration(restDocumentation))
.filter(document("original-response")) .filter(document("original-response"))
.filter(document("preprocessed-response", .filter(document("preprocessed-response",
preprocessResponse(prettyPrint(), maskLinks(), preprocessResponse(prettyPrint(), maskLinks(),
@@ -407,10 +406,10 @@ public class RestAssuredRestDocumentationIntegrationTests {
} }
@Test @Test
public void defaultPreprocessedResponse() { void defaultPreprocessedResponse(RestDocumentationContextProvider restDocumentation) {
Pattern pattern = Pattern.compile("(\"alpha\")"); Pattern pattern = Pattern.compile("(\"alpha\")");
given().port(tomcat.getPort()) given().port(tomcat.getPort())
.filter(documentationConfiguration(this.restDocumentation).operationPreprocessors() .filter(documentationConfiguration(restDocumentation).operationPreprocessors()
.withResponseDefaults(prettyPrint(), maskLinks(), .withResponseDefaults(prettyPrint(), maskLinks(),
modifyHeaders().remove("a").remove("Transfer-Encoding").remove("Date").remove("Server"), modifyHeaders().remove("a").remove("Transfer-Encoding").remove("Date").remove("Server"),
replacePattern(pattern, "\"<<beta>>\""), replacePattern(pattern, "\"<<beta>>\""),
@@ -432,7 +431,7 @@ public class RestAssuredRestDocumentationIntegrationTests {
} }
@Test @Test
public void customSnippetTemplate() throws MalformedURLException { void customSnippetTemplate(RestDocumentationContextProvider restDocumentation) throws MalformedURLException {
ClassLoader classLoader = new URLClassLoader( ClassLoader classLoader = new URLClassLoader(
new URL[] { new File("src/test/resources/custom-snippet-templates").toURI().toURL() }, new URL[] { new File("src/test/resources/custom-snippet-templates").toURI().toURL() },
getClass().getClassLoader()); getClass().getClassLoader());
@@ -441,7 +440,7 @@ public class RestAssuredRestDocumentationIntegrationTests {
try { try {
given().port(tomcat.getPort()) given().port(tomcat.getPort())
.accept("application/json") .accept("application/json")
.filter(documentationConfiguration(this.restDocumentation)) .filter(documentationConfiguration(restDocumentation))
.filter(document("custom-snippet-template")) .filter(document("custom-snippet-template"))
.get("/") .get("/")
.then() .then()
@@ -455,7 +454,7 @@ public class RestAssuredRestDocumentationIntegrationTests {
} }
@Test @Test
public void exceptionShouldBeThrownWhenCallDocumentRequestSpecificationNotConfigured() { void exceptionShouldBeThrownWhenCallDocumentRequestSpecificationNotConfigured() {
assertThatThrownBy(() -> given().port(tomcat.getPort()).filter(document("default")).get("/")) assertThatThrownBy(() -> given().port(tomcat.getPort()).filter(document("default")).get("/"))
.isInstanceOf(IllegalStateException.class) .isInstanceOf(IllegalStateException.class)
.hasMessage("REST Docs configuration not found. Did you forget to add a " .hasMessage("REST Docs configuration not found. Did you forget to add a "
@@ -463,7 +462,7 @@ public class RestAssuredRestDocumentationIntegrationTests {
} }
@Test @Test
public void exceptionShouldBeThrownWhenCallDocumentSnippetsRequestSpecificationNotConfigured() { void exceptionShouldBeThrownWhenCallDocumentSnippetsRequestSpecificationNotConfigured() {
RestDocumentationFilter documentation = document("{method-name}-{step}"); RestDocumentationFilter documentation = document("{method-name}-{step}");
assertThatThrownBy(() -> given().port(tomcat.getPort()) assertThatThrownBy(() -> given().port(tomcat.getPort())
.filter(documentation.document(responseHeaders(headerWithName("a").description("one")))) .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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with 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.Context;
import org.apache.catalina.LifecycleException; import org.apache.catalina.LifecycleException;
import org.apache.catalina.startup.Tomcat; 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.http.MediaType;
import org.springframework.util.FileCopyUtils; 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 * @author Andy Wilkinson
*/ */
class TomcatServer extends ExternalResource { class TomcatServer implements BeforeAllCallback, AfterAllCallback {
private Tomcat tomcat;
private int port; private int port;
@Override @Override
protected void before() throws LifecycleException { public void beforeAll(ExtensionContext extensionContext) {
this.tomcat = new Tomcat(); Store store = extensionContext.getStore(Namespace.create(TomcatServer.class));
this.tomcat.getConnector().setPort(0); store.getOrComputeIfAbsent(Tomcat.class, (key) -> {
Context context = this.tomcat.addContext("/", null); Tomcat tomcat = new Tomcat();
this.tomcat.addServlet("/", "test", new TestServlet()); tomcat.getConnector().setPort(0);
context.addServletMappingDecoded("/", "test"); Context context = tomcat.addContext("/", null);
this.tomcat.addServlet("/", "set-cookie", new CookiesServlet()); tomcat.addServlet("/", "test", new TestServlet());
context.addServletMappingDecoded("/set-cookie", "set-cookie"); context.addServletMappingDecoded("/", "test");
this.tomcat.addServlet("/", "query-parameter", new QueryParameterServlet()); tomcat.addServlet("/", "set-cookie", new CookiesServlet());
context.addServletMappingDecoded("/query-parameter", "query-parameter"); context.addServletMappingDecoded("/set-cookie", "set-cookie");
this.tomcat.addServlet("/", "form-url-encoded", new FormUrlEncodedServlet()); tomcat.addServlet("/", "query-parameter", new QueryParameterServlet());
context.addServletMappingDecoded("/form-url-encoded", "form-url-encoded"); context.addServletMappingDecoded("/query-parameter", "query-parameter");
this.tomcat.start(); tomcat.addServlet("/", "form-url-encoded", new FormUrlEncodedServlet());
this.port = this.tomcat.getConnector().getLocalPort(); 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 @Override
protected void after() { public void afterAll(ExtensionContext extensionContext) throws LifecycleException {
try { Store store = extensionContext.getStore(Namespace.create(TomcatServer.class));
this.tomcat.stop(); Tomcat tomcat = store.get(Tomcat.class, Tomcat.class);
} if (tomcat != null) {
catch (LifecycleException ex) { tomcat.stop();
throw new RuntimeException(ex);
} }
} }

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