diff --git a/.github/actions/build/action.yml b/.github/actions/build/action.yml index 70b05131..5f41ba6b 100644 --- a/.github/actions/build/action.yml +++ b/.github/actions/build/action.yml @@ -23,7 +23,7 @@ inputs: java-version: description: 'Java version to compile and test with' required: false - default: '17' + default: '24' publish: description: 'Whether to publish artifacts ready for deployment to Artifactory' required: false diff --git a/.github/actions/prepare-gradle-build/action.yml b/.github/actions/prepare-gradle-build/action.yml index f9969cf3..32e0748c 100644 --- a/.github/actions/prepare-gradle-build/action.yml +++ b/.github/actions/prepare-gradle-build/action.yml @@ -23,7 +23,7 @@ inputs: java-version: description: 'Java version to use for the build' required: false - default: '17' + default: '24' runs: using: composite steps: @@ -33,7 +33,7 @@ runs: distribution: ${{ inputs.java-early-access == 'true' && 'temurin' || (inputs.java-distribution || 'liberica') }} java-version: | ${{ inputs.java-early-access == 'true' && format('{0}-ea', inputs.java-version) || inputs.java-version }} - ${{ inputs.java-toolchain == 'true' && '17' || '' }} + ${{ inputs.java-toolchain == 'true' && '24' || '' }} - name: Set Up Gradle With Read/Write Cache if: ${{ inputs.cache-read-only == 'false' }} uses: gradle/actions/setup-gradle@d156388eb19639ec20ade50009f3d199ce1e2808 # v4.1.0 diff --git a/.github/workflows/build-and-deploy-snapshot.yml b/.github/workflows/build-and-deploy-snapshot.yml index 841ec2fb..22e61013 100644 --- a/.github/workflows/build-and-deploy-snapshot.yml +++ b/.github/workflows/build-and-deploy-snapshot.yml @@ -2,7 +2,7 @@ name: Build and Deploy Snapshot on: push: branches: - - 3.0.x + - main permissions: contents: read concurrency: @@ -27,7 +27,7 @@ jobs: with: artifact-properties: | /**/spring-restdocs-*.zip::zip.type=docs,zip.deployed=false - build-name: 'spring-restdocs-3.0.x' + build-name: 'spring-restdocs-4.0.x' folder: 'deployment-repository' password: ${{ secrets.ARTIFACTORY_PASSWORD }} repository: ${{ 'libs-snapshot-local' }} @@ -40,7 +40,7 @@ jobs: uses: ./.github/actions/send-notification with: build-scan-url: ${{ steps.build-and-publish.outputs.build-scan-url }} - run-name: ${{ format('{0} | Linux | Java 17', github.ref_name) }} + run-name: ${{ format('{0} | Linux | Java 24', github.ref_name) }} status: ${{ job.status }} webhook-url: ${{ secrets.GOOGLE_CHAT_WEBHOOK_URL }} outputs: diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a77a28c4..1aea38fb 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,7 +2,7 @@ name: CI on: push: branches: - - '3.0.x' + - main permissions: contents: read concurrency: @@ -22,16 +22,16 @@ jobs: name: Windows java: - version: 17 - toolchain: false + toolchain: true - version: 21 - toolchain: false + toolchain: true - version: 24 toolchain: false exclude: - os: name: Linux java: - version: 17 + version: 24 steps: - name: Prepare Windows runner if: ${{ runner.os == 'Windows' }} diff --git a/.github/workflows/release-milestone.yml b/.github/workflows/release-milestone.yml new file mode 100644 index 00000000..2090cdd0 --- /dev/null +++ b/.github/workflows/release-milestone.yml @@ -0,0 +1,83 @@ +name: Release Milestone +on: + push: + tags: + - v4.0.0-M[0-9] + - v4.0.0-RC[0-9] +permissions: + contents: read +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} +jobs: + build-and-stage-release: + name: Build and Stage Release + if: ${{ github.repository == 'spring-projects/spring-restdocs' }} + runs-on: ${{ vars.UBUNTU_MEDIUIM || 'ubuntu-latest' }} + steps: + - name: Check Out Code + uses: actions/checkout@v4 + - name: Build and Publish + id: build-and-publish + uses: ./.github/actions/build + with: + develocity-access-key: ${{ secrets.DEVELOCITY_ACCESS_KEY }} + gradle-cache-read-only: false + publish: true + - name: Stage Release + uses: spring-io/artifactory-deploy-action@26bbe925a75f4f863e1e529e85be2d0093cac116 # v0.0.1 + with: + artifact-properties: | + /**/spring-restdocs-*.zip::zip.type=docs,zip.deployed=false + build-name: ${{ format('spring-restdocs-{0}', steps.build-and-publish.outputs.version) }} + folder: 'deployment-repository' + password: ${{ secrets.ARTIFACTORY_PASSWORD }} + repository: 'libs-staging-local' + signing-key: ${{ secrets.GPG_PRIVATE_KEY }} + signing-passphrase: ${{ secrets.GPG_PASSPHRASE }} + uri: 'https://repo.spring.io' + username: ${{ secrets.ARTIFACTORY_USERNAME }} + outputs: + version: ${{ steps.build-and-publish.outputs.version }} + sync-to-maven-central: + name: Sync to Maven Central + needs: + - build-and-stage-release + runs-on: ${{ vars.UBUNTU_SMALL || 'ubuntu-latest' }} + steps: + - name: Check Out Code + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + - name: Sync to Maven Central + uses: ./.github/actions/sync-to-maven-central + with: + central-token-password: ${{ secrets.CENTRAL_TOKEN_PASSWORD }} + central-token-username: ${{ secrets.CENTRAL_TOKEN_USERNAME }} + jfrog-cli-config-token: ${{ secrets.JF_ARTIFACTORY_SPRING }} + spring-restdocs-version: ${{ needs.build-and-stage-release.outputs.version }} + promote-release: + name: Promote Release + needs: + - build-and-stage-release + - sync-to-maven-central + runs-on: ${{ vars.UBUNTU_SMALL || 'ubuntu-latest' }} + steps: + - name: Set up JFrog CLI + uses: jfrog/setup-jfrog-cli@9fe0f98bd45b19e6e931d457f4e98f8f84461fb5 # v4.4.1 + env: + JF_ENV_SPRING: ${{ secrets.JF_ARTIFACTORY_SPRING }} + - name: Promote Open Source Build + run: jfrog rt build-promote ${{ format('spring-restdocs-{0}', needs.build-and-stage-release.outputs.version)}} ${{ github.run_number }} libs-milestone-local + create-github-release: + name: Create GitHub Release + needs: + - build-and-stage-release + - promote-release + runs-on: ${{ vars.UBUNTU_SMALL || 'ubuntu-latest' }} + steps: + - name: Check Out Code + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + - name: Create GitHub Release + uses: ./.github/actions/create-github-release + with: + milestone: ${{ needs.build-and-stage-release.outputs.version }} + pre-release: true + token: ${{ secrets.GH_ACTIONS_REPO_TOKEN }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 58549bec..86b280ce 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -2,7 +2,7 @@ name: Release on: push: tags: - - v3.0.[0-9]+ + - v4.0.[0-9]+ permissions: contents: read concurrency: diff --git a/.sdkmanrc b/.sdkmanrc index bea2d515..b41ba343 100644 --- a/.sdkmanrc +++ b/.sdkmanrc @@ -1,3 +1,3 @@ # Enable auto-env through the sdkman_auto_env config # Add key=value pairs of SDKs to use below -java=17.0.15-librca +java=24.0.1-librca diff --git a/build.gradle b/build.gradle index 83c3c29b..9c8702e4 100644 --- a/build.gradle +++ b/build.gradle @@ -1,127 +1,19 @@ plugins { id 'base' - id 'io.spring.javaformat' version "$javaFormatVersion" apply false - id 'io.spring.nohttp' version '0.0.11' apply false id 'maven-publish' + id 'org.springframework.restdocs.conventions' } allprojects { group = "org.springframework.restdocs" repositories { mavenCentral() - maven { - url = "https://repo.spring.io/milestone" - content { - includeGroup "io.micrometer" - includeGroup "io.projectreactor" - includeGroup "org.springframework" - } - } if (version.endsWith('-SNAPSHOT')) { maven { url = "https://repo.spring.io/snapshot" } } } } -apply plugin: "io.spring.nohttp" -apply from: "${rootProject.projectDir}/gradle/publish-maven.gradle" - -nohttp { - source.exclude "buildSrc/.gradle/**" - source.exclude "**/build/**" - source.exclude "**/target/**" -} - -ext { - javadocLinks = [ - "https://docs.spring.io/spring-framework/docs/$springFrameworkVersion/javadoc-api/", - "https://docs.jboss.org/hibernate/validator/7.0/api/", - "https://jakarta.ee/specifications/bean-validation/3.0/apidocs/" - ] as String[] -} - -check { - dependsOn checkstyleNohttp -} - -subprojects { subproject -> - plugins.withType(JavaPlugin) { - subproject.apply plugin: "io.spring.javaformat" - subproject.apply plugin: "checkstyle" - - java { - sourceCompatibility = 17 - targetCompatibility = 17 - } - - configurations { - all { - resolutionStrategy.cacheChangingModulesFor 0, "minutes" - } - internal { - canBeConsumed = false - canBeResolved = false - } - compileClasspath.extendsFrom(internal) - runtimeClasspath.extendsFrom(internal) - testCompileClasspath.extendsFrom(internal) - testRuntimeClasspath.extendsFrom(internal) - } - - test { - testLogging { - exceptionFormat = "full" - } - } - - tasks.withType(JavaCompile) { - options.compilerArgs = [ "-Werror", "-Xlint:unchecked", "-Xlint:deprecation", "-Xlint:rawtypes", "-Xlint:varargs", "-Xlint:options" ] - options.encoding = "UTF-8" - } - - tasks.withType(Test) { - maxHeapSize = "1024M" - } - - checkstyle { - configFile = rootProject.file("config/checkstyle/checkstyle.xml") - configProperties = [ "checkstyle.config.dir" : rootProject.file("config/checkstyle") ] - toolVersion = "10.12.4" - } - - dependencies { - checkstyle("com.puppycrawl.tools:checkstyle:${checkstyle.toolVersion}") - checkstyle("io.spring.javaformat:spring-javaformat-checkstyle:${javaFormatVersion}") - } - - plugins.withType(MavenPublishPlugin) { - javadoc { - description = "Generates project-level javadoc for use in -javadoc jar" - options.memberLevel = org.gradle.external.javadoc.JavadocMemberLevel.PROTECTED - options.author = true - options.header = "Spring REST Docs $version" - options.docTitle = "${options.header} API" - options.links = javadocLinks - options.addStringOption "-quiet" - options.encoding = "UTF-8" - options.source = "17" - } - - java { - withJavadocJar() - withSourcesJar() - } - } - } - plugins.withType(MavenPublishPlugin) { - subproject.apply from: "${rootProject.projectDir}/gradle/publish-maven.gradle" - } - tasks.withType(GenerateModuleMetadata) { - enabled = false - } - -} - task api (type: Javadoc) { group = "Documentation" description = "Generates aggregated Javadoc API documentation." @@ -130,7 +22,6 @@ task api (type: Javadoc) { Set publishedProjects = rootProject.subprojects.findAll { it != project} .findAll { it.plugins.hasPlugin(JavaPlugin) && it.plugins.hasPlugin(MavenPublishPlugin) } .findAll { !excludedProjects.contains(it.name) } - .findAll { !it.name.startsWith('spring-boot-starter') } dependsOn publishedProjects.javadoc source publishedProjects.javadoc.source classpath = project.files(publishedProjects.javadoc.classpath) diff --git a/buildSrc/build.gradle b/buildSrc/build.gradle deleted file mode 100644 index 6746f180..00000000 --- a/buildSrc/build.gradle +++ /dev/null @@ -1,3 +0,0 @@ -plugins { - id "java-gradle-plugin" -} diff --git a/docs/build.gradle b/docs/build.gradle index 8c08f180..9584d9fb 100644 --- a/docs/build.gradle +++ b/docs/build.gradle @@ -1,6 +1,7 @@ plugins { id "org.asciidoctor.jvm.convert" version "4.0.4" - id "java-library" + id "org.springframework.restdocs.conventions" + id "java" } configurations { @@ -10,20 +11,16 @@ configurations { dependencies { asciidoctorExt("io.spring.asciidoctor.backends:spring-asciidoctor-backends:0.0.5") - internal(platform(project(":spring-restdocs-platform"))) - internal(enforcedPlatform("org.springframework:spring-framework-bom:$springFrameworkVersion")) - testImplementation(project(":spring-restdocs-mockmvc")) testImplementation(project(":spring-restdocs-restassured")) testImplementation(project(":spring-restdocs-webtestclient")) testImplementation("jakarta.servlet:jakarta.servlet-api") testImplementation("jakarta.validation:jakarta.validation-api") - testImplementation("junit:junit") testImplementation("org.testng:testng:6.9.10") - testImplementation("org.junit.jupiter:junit-jupiter-api") -} + testImplementation("org.junit.jupiter:junit-jupiter") -tasks.findByPath("artifactoryPublish")?.enabled = false + testRuntimeOnly("org.junit.platform:junit-platform-launcher") +} tasks.withType(org.asciidoctor.gradle.jvm.AbstractAsciidoctorTask) { baseDirFollowsSourceDir() diff --git a/docs/src/docs/asciidoc/documenting-your-api.adoc b/docs/src/docs/asciidoc/documenting-your-api.adoc index ce3dc590..58521ef9 100644 --- a/docs/src/docs/asciidoc/documenting-your-api.adoc +++ b/docs/src/docs/asciidoc/documenting-your-api.adoc @@ -5,6 +5,20 @@ This section provides more details about using Spring REST Docs to document your +[[documenting-your-api-null-safety]] +=== Null Safety +Spring REST Docs is annotated with https://jspecify.dev/docs/start-here/[JSpecify ] annotations to declare the nullability of its API. +To learn more about JSpecify, its https://jspecify.dev/docs/user-guide/[user guide] is recommended reading. + +The primary goal of declaring the nullability of the API is to prevent a `NullPointerException` from being thrown at runtime. +This is achieved through build-time checks that are available with both Java and Kotlin. +Performing the checks with Java requires some tooling such as https://github.com/uber/NullAway[NullAway] or an IDE that supports JSpecify annotations such as IntelliJ IDEA. +The checks are available automatically with Kotlin which translates the JSpecify annotations into Kotlin's null safety. + +To learn more about null safety with Spring, refer to the {spring-framework-docs}/core/null-safety.html[Spring Framework reference documentation]. + + + [[documenting-your-api-hypermedia]] === Hypermedia @@ -802,8 +816,6 @@ Uses the static `parameterWithName` method on `org.springframework.restdocs.requ The result is a snippet named `path-parameters.adoc` that contains a table describing the path parameters that are supported by the resource. -TIP: If you use MockMvc with Spring Framework 6.1 or earlier, to make the path parameters available for documentation, you must build the request by using one of the methods on `RestDocumentationRequestBuilders` rather than `MockMvcRequestBuilders`. - When documenting path parameters, the test fails if an undocumented path parameter is used in the request. Similarly, the test also fails if a documented path parameter is not found in the request and the path parameter has not been marked as optional. @@ -1148,7 +1160,7 @@ To take complete control of constraint resolution, you can use your own implemen [[documenting-your-api-constraints-describing]] ==== Describing Constraints -Default descriptions are provided for all of Bean Validation 3.0's constraints: +Default descriptions are provided for all of Bean Validation 3.1's constraints: * `AssertFalse` * `AssertTrue` diff --git a/docs/src/docs/asciidoc/getting-started.adoc b/docs/src/docs/asciidoc/getting-started.adoc index ca7756ac..b692f434 100644 --- a/docs/src/docs/asciidoc/getting-started.adoc +++ b/docs/src/docs/asciidoc/getting-started.adoc @@ -18,9 +18,9 @@ If you want to jump straight in, a number of https://github.com/spring-projects/ Spring REST Docs has the following minimum requirements: * Java 17 -* Spring Framework 6 +* Spring Framework 7 -Additionally, the `spring-restdocs-restassured` module requires REST Assured 5.2. +Additionally, the `spring-restdocs-restassured` module requires REST Assured 5.5. @@ -78,6 +78,7 @@ If you want to use `WebTestClient` or REST Assured rather than MockMvc, add a de <4> Add `spring-restdocs-asciidoctor` as a dependency of the Asciidoctor plugin. This will automatically configure the `snippets` attribute for use in your `.adoc` files to point to `target/generated-snippets`. It will also allow you to use the `operation` block macro. +It requires AsciidoctorJ 3.0. [source,indent=0,subs="verbatim,attributes",role="secondary"] .Gradle @@ -114,6 +115,7 @@ It will also allow you to use the `operation` block macro. <3> Add a dependency on `spring-restdocs-asciidoctor` in the `asciidoctorExt` configuration. This will automatically configure the `snippets` attribute for use in your `.adoc` files to point to `build/generated-snippets`. It will also allow you to use the `operation` block macro. +It requires AsciidoctorJ 3.0. <4> Add a dependency on `spring-restdocs-mockmvc` in the `testImplementation` configuration. If you want to use `WebTestClient` or REST Assured rather than MockMvc, add a dependency on `spring-restdocs-webtestclient` or `spring-restdocs-restassured` respectively instead. <5> Configure a `snippetsDir` property that defines the output location for generated snippets. @@ -201,8 +203,7 @@ It then produces documentation snippets for the request and the resulting respon ==== Setting up Your Tests Exactly how you set up your tests depends on the test framework that you use. -Spring REST Docs provides first-class support for JUnit 5 and JUnit 4. -JUnit 5 is recommended. +Spring REST Docs provides first-class support for JUnit 5. Other frameworks, such as TestNG, are also supported, although slightly more setup is required. @@ -256,72 +257,6 @@ public class JUnit5ExampleTests { Next, you must provide a `@BeforeEach` method to configure MockMvc or WebTestClient, or REST Assured. The following listings show how to do so: -[source,java,indent=0,role="primary"] -.MockMvc ----- -include::{examples-dir}/com/example/mockmvc/ExampleApplicationJUnit5Tests.java[tags=setup] ----- -<1> The `MockMvc` instance is configured by using a `MockMvcRestDocumentationConfigurer`. -You can obtain an instance of this class from the static `documentationConfiguration()` method on `org.springframework.restdocs.mockmvc.MockMvcRestDocumentation`. - -[source,java,indent=0,role="secondary"] -.WebTestClient ----- -include::{examples-dir}/com/example/webtestclient/ExampleApplicationJUnit5Tests.java[tags=setup] ----- -<1> The `WebTestClient` instance is configured by adding a `WebTestClientRestDocumentationConfigurer` as an `ExchangeFilterFunction`. -You can obtain an instance of this class from the static `documentationConfiguration()` method on `org.springframework.restdocs.webtestclient.WebTestClientRestDocumentation`. - -[source,java,indent=0,role="secondary"] -.REST Assured ----- -include::{examples-dir}/com/example/restassured/ExampleApplicationJUnit5Tests.java[tags=setup] ----- -<1> REST Assured is configured by adding a `RestAssuredRestDocumentationConfigurer` as a `Filter`. -You can obtain an instance of this class from the static `documentationConfiguration()` method on `RestAssuredRestDocumentation` in the `org.springframework.restdocs.restassured` package. - -The configurer applies sensible defaults and also provides an API for customizing the configuration. -See the <> for more information. - - - -[[getting-started-documentation-snippets-setup-junit]] -===== Setting up Your JUnit 4 Tests - -When using JUnit 4, the first step in generating documentation snippets is to declare a `public` `JUnitRestDocumentation` field that is annotated as a JUnit `@Rule`. -The following example shows how to do so: - -[source,java,indent=0] ----- -@Rule -public JUnitRestDocumentation restDocumentation = new JUnitRestDocumentation(); ----- - -By default, the `JUnitRestDocumentation` rule is automatically configured with an output directory based on your project's build tool: - -[cols="2,5"] -|=== -| Build tool | Output directory - -| Maven -| `target/generated-snippets` - -| Gradle -| `build/generated-snippets` -|=== - -You can override the default by providing an output directory when you create the `JUnitRestDocumentation` instance. -The following example shows how to do so: - -[source,java,indent=0] ----- -@Rule -public JUnitRestDocumentation restDocumentation = new JUnitRestDocumentation("custom"); ----- - -Next, you must provide an `@Before` method to configure MockMvc or WebTestClient, or REST Assured. -The following examples show how to do so: - [source,java,indent=0,role="primary"] .MockMvc ---- @@ -335,7 +270,7 @@ You can obtain an instance of this class from the static `documentationConfigura ---- include::{examples-dir}/com/example/webtestclient/ExampleApplicationTests.java[tags=setup] ---- -<1> The `WebTestClient` instance is configured by adding a `WebTestclientRestDocumentationConfigurer` as an `ExchangeFilterFunction`. +<1> The `WebTestClient` instance is configured by adding a `WebTestClientRestDocumentationConfigurer` as an `ExchangeFilterFunction`. You can obtain an instance of this class from the static `documentationConfiguration()` method on `org.springframework.restdocs.webtestclient.WebTestClientRestDocumentation`. [source,java,indent=0,role="secondary"] @@ -351,16 +286,15 @@ See the <> for more information. + [[getting-started-documentation-snippets-setup-manual]] ===== Setting up your tests without JUnit -The configuration when JUnit is not being used is largely similar to when it is being used. -This section describes the key differences. -The {samples}/testng[TestNG sample] also illustrates the approach. +The configuration when JUnit is not being used is a little more involved as the test class must perform some lifecycle management. +The {samples}/testng[TestNG sample] illustrates the approach. -The first difference is that you should use `ManualRestDocumentation` in place of `JUnitRestDocumentation`. -Also, you do not need the `@Rule` annotation. -The following example shows how to use `ManualRestDocumentation`: +First, you need a `ManualRestDocumentation` field. +The following example shows how to define it: [source,java,indent=0] ---- diff --git a/docs/src/docs/asciidoc/index.adoc b/docs/src/docs/asciidoc/index.adoc index 756e13e2..ef6f163b 100644 --- a/docs/src/docs/asciidoc/index.adoc +++ b/docs/src/docs/asciidoc/index.adoc @@ -13,7 +13,7 @@ Andy Wilkinson; Jay Bryant :samples: https://github.com/spring-projects/spring-restdocs-samples/tree/main :templates: {source}spring-restdocs/src/main/resources/org/springframework/restdocs/templates :spring-boot-docs: https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle -:spring-framework-docs: https://docs.spring.io/spring-framework/reference/6.2 +:spring-framework-docs: https://docs.spring.io/spring-framework/reference/7.0 :spring-framework-api: https://docs.spring.io/spring-framework/docs/{spring-framework-version}/javadoc-api [[abstract]] diff --git a/docs/src/docs/asciidoc/working-with-asciidoctor.adoc b/docs/src/docs/asciidoc/working-with-asciidoctor.adoc index 7adc6231..e57d882d 100644 --- a/docs/src/docs/asciidoc/working-with-asciidoctor.adoc +++ b/docs/src/docs/asciidoc/working-with-asciidoctor.adoc @@ -28,6 +28,7 @@ This section covers how to include Asciidoc snippets. You can use a macro named `operation` to import all or some of the snippets that have been generated for a specific operation. It is made available by including `spring-restdocs-asciidoctor` in your project's <>. +`spring-restdocs-asciidoctor` requires AsciidoctorJ 3.0. The target of the macro is the name of the operation. In its simplest form, you can use the macro to include all of the snippets for an operation, as shown in the following example: diff --git a/docs/src/test/java/com/example/mockmvc/CustomDefaultOperationPreprocessors.java b/docs/src/test/java/com/example/mockmvc/CustomDefaultOperationPreprocessors.java index bac3db87..5876a265 100644 --- a/docs/src/test/java/com/example/mockmvc/CustomDefaultOperationPreprocessors.java +++ b/docs/src/test/java/com/example/mockmvc/CustomDefaultOperationPreprocessors.java @@ -16,10 +16,11 @@ package com.example.mockmvc; -import org.junit.Before; -import org.junit.Rule; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.extension.ExtendWith; -import org.springframework.restdocs.JUnitRestDocumentation; +import org.springframework.restdocs.RestDocumentationContextProvider; +import org.springframework.restdocs.RestDocumentationExtension; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.setup.MockMvcBuilders; import org.springframework.web.context.WebApplicationContext; @@ -28,21 +29,19 @@ import static org.springframework.restdocs.operation.preprocess.Preprocessors.modifyHeaders; import static org.springframework.restdocs.operation.preprocess.Preprocessors.prettyPrint; -public class CustomDefaultOperationPreprocessors { - - @Rule - public final JUnitRestDocumentation restDocumentation = new JUnitRestDocumentation(); +@ExtendWith(RestDocumentationExtension.class) +class CustomDefaultOperationPreprocessors { private WebApplicationContext context; @SuppressWarnings("unused") private MockMvc mockMvc; - @Before - public void setup() { + @BeforeEach + void setup(RestDocumentationContextProvider restDocumentation) { // tag::custom-default-operation-preprocessors[] this.mockMvc = MockMvcBuilders.webAppContextSetup(this.context) - .apply(documentationConfiguration(this.restDocumentation).operationPreprocessors() + .apply(documentationConfiguration(restDocumentation).operationPreprocessors() .withRequestDefaults(modifyHeaders().remove("Foo")) // <1> .withResponseDefaults(prettyPrint())) // <2> .build(); diff --git a/docs/src/test/java/com/example/mockmvc/CustomDefaultSnippets.java b/docs/src/test/java/com/example/mockmvc/CustomDefaultSnippets.java index 8a5f3eca..8a6f6b4d 100644 --- a/docs/src/test/java/com/example/mockmvc/CustomDefaultSnippets.java +++ b/docs/src/test/java/com/example/mockmvc/CustomDefaultSnippets.java @@ -16,11 +16,12 @@ package com.example.mockmvc; -import org.junit.Before; -import org.junit.Rule; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.restdocs.JUnitRestDocumentation; +import org.springframework.restdocs.RestDocumentationContextProvider; +import org.springframework.restdocs.RestDocumentationExtension; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.setup.MockMvcBuilders; import org.springframework.web.context.WebApplicationContext; @@ -28,10 +29,8 @@ import static org.springframework.restdocs.cli.CliDocumentation.curlRequest; import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.documentationConfiguration; -public class CustomDefaultSnippets { - - @Rule - public final JUnitRestDocumentation restDocumentation = new JUnitRestDocumentation(); +@ExtendWith(RestDocumentationExtension.class) +class CustomDefaultSnippets { @Autowired private WebApplicationContext context; @@ -39,11 +38,11 @@ public class CustomDefaultSnippets { @SuppressWarnings("unused") private MockMvc mockMvc; - @Before - public void setUp() { + @BeforeEach + void setUp(RestDocumentationContextProvider restDocumentation) { // tag::custom-default-snippets[] this.mockMvc = MockMvcBuilders.webAppContextSetup(this.context) - .apply(documentationConfiguration(this.restDocumentation).snippets().withDefaults(curlRequest())) + .apply(documentationConfiguration(restDocumentation).snippets().withDefaults(curlRequest())) .build(); // end::custom-default-snippets[] } diff --git a/docs/src/test/java/com/example/mockmvc/CustomEncoding.java b/docs/src/test/java/com/example/mockmvc/CustomEncoding.java index 6e833f9c..df116e55 100644 --- a/docs/src/test/java/com/example/mockmvc/CustomEncoding.java +++ b/docs/src/test/java/com/example/mockmvc/CustomEncoding.java @@ -16,21 +16,20 @@ package com.example.mockmvc; -import org.junit.Before; -import org.junit.Rule; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.restdocs.JUnitRestDocumentation; +import org.springframework.restdocs.RestDocumentationContextProvider; +import org.springframework.restdocs.RestDocumentationExtension; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.setup.MockMvcBuilders; import org.springframework.web.context.WebApplicationContext; import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.documentationConfiguration; -public class CustomEncoding { - - @Rule - public final JUnitRestDocumentation restDocumentation = new JUnitRestDocumentation(); +@ExtendWith(RestDocumentationExtension.class) +class CustomEncoding { @Autowired private WebApplicationContext context; @@ -38,11 +37,11 @@ public class CustomEncoding { @SuppressWarnings("unused") private MockMvc mockMvc; - @Before - public void setUp() { + @BeforeEach + void setUp(RestDocumentationContextProvider restDocumentation) { // tag::custom-encoding[] this.mockMvc = MockMvcBuilders.webAppContextSetup(this.context) - .apply(documentationConfiguration(this.restDocumentation).snippets().withEncoding("ISO-8859-1")) + .apply(documentationConfiguration(restDocumentation).snippets().withEncoding("ISO-8859-1")) .build(); // end::custom-encoding[] } diff --git a/docs/src/test/java/com/example/mockmvc/CustomFormat.java b/docs/src/test/java/com/example/mockmvc/CustomFormat.java index ec6a01c4..b78f8831 100644 --- a/docs/src/test/java/com/example/mockmvc/CustomFormat.java +++ b/docs/src/test/java/com/example/mockmvc/CustomFormat.java @@ -16,11 +16,12 @@ package com.example.mockmvc; -import org.junit.Before; -import org.junit.Rule; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.restdocs.JUnitRestDocumentation; +import org.springframework.restdocs.RestDocumentationContextProvider; +import org.springframework.restdocs.RestDocumentationExtension; import org.springframework.restdocs.templates.TemplateFormats; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.setup.MockMvcBuilders; @@ -28,10 +29,8 @@ import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.documentationConfiguration; -public class CustomFormat { - - @Rule - public final JUnitRestDocumentation restDocumentation = new JUnitRestDocumentation(); +@ExtendWith(RestDocumentationExtension.class) +class CustomFormat { @Autowired private WebApplicationContext context; @@ -39,11 +38,11 @@ public class CustomFormat { @SuppressWarnings("unused") private MockMvc mockMvc; - @Before - public void setUp() { + @BeforeEach + void setUp(RestDocumentationContextProvider restDocumentation) { // tag::custom-format[] this.mockMvc = MockMvcBuilders.webAppContextSetup(this.context) - .apply(documentationConfiguration(this.restDocumentation).snippets() + .apply(documentationConfiguration(restDocumentation).snippets() .withTemplateFormat(TemplateFormats.markdown())) .build(); // end::custom-format[] diff --git a/docs/src/test/java/com/example/mockmvc/CustomUriConfiguration.java b/docs/src/test/java/com/example/mockmvc/CustomUriConfiguration.java index 8e1c9518..fda77d75 100644 --- a/docs/src/test/java/com/example/mockmvc/CustomUriConfiguration.java +++ b/docs/src/test/java/com/example/mockmvc/CustomUriConfiguration.java @@ -16,21 +16,20 @@ package com.example.mockmvc; -import org.junit.Before; -import org.junit.Rule; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.restdocs.JUnitRestDocumentation; +import org.springframework.restdocs.RestDocumentationContextProvider; +import org.springframework.restdocs.RestDocumentationExtension; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.setup.MockMvcBuilders; import org.springframework.web.context.WebApplicationContext; import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.documentationConfiguration; -public class CustomUriConfiguration { - - @Rule - public final JUnitRestDocumentation restDocumentation = new JUnitRestDocumentation(); +@ExtendWith(RestDocumentationExtension.class) +class CustomUriConfiguration { @Autowired private WebApplicationContext context; @@ -38,11 +37,11 @@ public class CustomUriConfiguration { @SuppressWarnings("unused") private MockMvc mockMvc; - @Before - public void setUp() { + @BeforeEach + void setUp(RestDocumentationContextProvider restDocumentation) { // tag::custom-uri-configuration[] this.mockMvc = MockMvcBuilders.webAppContextSetup(this.context) - .apply(documentationConfiguration(this.restDocumentation).uris() + .apply(documentationConfiguration(restDocumentation).uris() .withScheme("https") .withHost("example.com") .withPort(443)) diff --git a/docs/src/test/java/com/example/mockmvc/EveryTestPreprocessing.java b/docs/src/test/java/com/example/mockmvc/EveryTestPreprocessing.java index 5bb8f5b1..00ece54d 100644 --- a/docs/src/test/java/com/example/mockmvc/EveryTestPreprocessing.java +++ b/docs/src/test/java/com/example/mockmvc/EveryTestPreprocessing.java @@ -16,10 +16,11 @@ package com.example.mockmvc; -import org.junit.Before; -import org.junit.Rule; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.extension.ExtendWith; -import org.springframework.restdocs.JUnitRestDocumentation; +import org.springframework.restdocs.RestDocumentationContextProvider; +import org.springframework.restdocs.RestDocumentationExtension; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.setup.MockMvcBuilders; import org.springframework.web.context.WebApplicationContext; @@ -33,20 +34,18 @@ import static org.springframework.restdocs.operation.preprocess.Preprocessors.prettyPrint; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +@ExtendWith(RestDocumentationExtension.class) public class EveryTestPreprocessing { - @Rule - public final JUnitRestDocumentation restDocumentation = new JUnitRestDocumentation(); - private WebApplicationContext context; // tag::setup[] private MockMvc mockMvc; - @Before - public void setup() { + @BeforeEach + void setup(RestDocumentationContextProvider restDocumentation) { this.mockMvc = MockMvcBuilders.webAppContextSetup(this.context) - .apply(documentationConfiguration(this.restDocumentation).operationPreprocessors() + .apply(documentationConfiguration(restDocumentation).operationPreprocessors() .withRequestDefaults(modifyHeaders().remove("Foo")) // <1> .withResponseDefaults(prettyPrint())) // <2> .build(); diff --git a/docs/src/test/java/com/example/mockmvc/ExampleApplicationJUnit5Tests.java b/docs/src/test/java/com/example/mockmvc/ExampleApplicationJUnit5Tests.java deleted file mode 100644 index bae120f8..00000000 --- a/docs/src/test/java/com/example/mockmvc/ExampleApplicationJUnit5Tests.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright 2014-present 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 com.example.mockmvc; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.extension.ExtendWith; - -import org.springframework.restdocs.RestDocumentationContextProvider; -import org.springframework.restdocs.RestDocumentationExtension; -import org.springframework.test.web.servlet.MockMvc; -import org.springframework.test.web.servlet.setup.MockMvcBuilders; -import org.springframework.web.context.WebApplicationContext; - -import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.documentationConfiguration; - -@ExtendWith(RestDocumentationExtension.class) -class ExampleApplicationJUnit5Tests { - - @SuppressWarnings("unused") - // tag::setup[] - private MockMvc mockMvc; - - @BeforeEach - void setUp(WebApplicationContext webApplicationContext, RestDocumentationContextProvider restDocumentation) { - this.mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext) - .apply(documentationConfiguration(restDocumentation)) // <1> - .build(); - } - // end::setup[] - -} diff --git a/docs/src/test/java/com/example/mockmvc/ExampleApplicationTests.java b/docs/src/test/java/com/example/mockmvc/ExampleApplicationTests.java index 987439ce..f301e97a 100644 --- a/docs/src/test/java/com/example/mockmvc/ExampleApplicationTests.java +++ b/docs/src/test/java/com/example/mockmvc/ExampleApplicationTests.java @@ -16,33 +16,28 @@ package com.example.mockmvc; -import org.junit.Before; -import org.junit.Rule; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.extension.ExtendWith; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.restdocs.JUnitRestDocumentation; +import org.springframework.restdocs.RestDocumentationContextProvider; +import org.springframework.restdocs.RestDocumentationExtension; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.setup.MockMvcBuilders; import org.springframework.web.context.WebApplicationContext; import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.documentationConfiguration; -public class ExampleApplicationTests { - - @Rule - public final JUnitRestDocumentation restDocumentation = new JUnitRestDocumentation(); +@ExtendWith(RestDocumentationExtension.class) +class ExampleApplicationTests { @SuppressWarnings("unused") // tag::setup[] private MockMvc mockMvc; - @Autowired - private WebApplicationContext context; - - @Before - public void setUp() { - this.mockMvc = MockMvcBuilders.webAppContextSetup(this.context) - .apply(documentationConfiguration(this.restDocumentation)) // <1> + @BeforeEach + void setUp(WebApplicationContext webApplicationContext, RestDocumentationContextProvider restDocumentation) { + this.mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext) + .apply(documentationConfiguration(restDocumentation)) // <1> .build(); } // end::setup[] diff --git a/docs/src/test/java/com/example/mockmvc/ParameterizedOutput.java b/docs/src/test/java/com/example/mockmvc/ParameterizedOutput.java index 71930e43..44cd257e 100644 --- a/docs/src/test/java/com/example/mockmvc/ParameterizedOutput.java +++ b/docs/src/test/java/com/example/mockmvc/ParameterizedOutput.java @@ -16,10 +16,11 @@ package com.example.mockmvc; -import org.junit.Before; -import org.junit.Rule; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.extension.ExtendWith; -import org.springframework.restdocs.JUnitRestDocumentation; +import org.springframework.restdocs.RestDocumentationContextProvider; +import org.springframework.restdocs.RestDocumentationExtension; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.setup.MockMvcBuilders; import org.springframework.web.context.WebApplicationContext; @@ -27,10 +28,8 @@ import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document; import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.documentationConfiguration; -public class ParameterizedOutput { - - @Rule - public final JUnitRestDocumentation restDocumentation = new JUnitRestDocumentation(); +@ExtendWith(RestDocumentationExtension.class) +class ParameterizedOutput { @SuppressWarnings("unused") private MockMvc mockMvc; @@ -38,10 +37,10 @@ public class ParameterizedOutput { private WebApplicationContext context; // tag::parameterized-output[] - @Before - public void setUp() { + @BeforeEach + void setUp(RestDocumentationContextProvider restDocumentation) { this.mockMvc = MockMvcBuilders.webAppContextSetup(this.context) - .apply(documentationConfiguration(this.restDocumentation)) + .apply(documentationConfiguration(restDocumentation)) .alwaysDo(document("{method-name}/{step}/")) .build(); } diff --git a/docs/src/test/java/com/example/restassured/CustomDefaultOperationPreprocessors.java b/docs/src/test/java/com/example/restassured/CustomDefaultOperationPreprocessors.java index 2aaa6333..285813b9 100644 --- a/docs/src/test/java/com/example/restassured/CustomDefaultOperationPreprocessors.java +++ b/docs/src/test/java/com/example/restassured/CustomDefaultOperationPreprocessors.java @@ -18,28 +18,27 @@ import io.restassured.builder.RequestSpecBuilder; import io.restassured.specification.RequestSpecification; -import org.junit.Before; -import org.junit.Rule; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.extension.ExtendWith; -import org.springframework.restdocs.JUnitRestDocumentation; +import org.springframework.restdocs.RestDocumentationContextProvider; +import org.springframework.restdocs.RestDocumentationExtension; import static org.springframework.restdocs.operation.preprocess.Preprocessors.modifyHeaders; import static org.springframework.restdocs.operation.preprocess.Preprocessors.prettyPrint; import static org.springframework.restdocs.restassured.RestAssuredRestDocumentation.documentationConfiguration; -public class CustomDefaultOperationPreprocessors { - - @Rule - public final JUnitRestDocumentation restDocumentation = new JUnitRestDocumentation(); +@ExtendWith(RestDocumentationExtension.class) +class CustomDefaultOperationPreprocessors { @SuppressWarnings("unused") private RequestSpecification spec; - @Before - public void setup() { + @BeforeEach + void setup(RestDocumentationContextProvider restDocumentation) { // tag::custom-default-operation-preprocessors[] this.spec = new RequestSpecBuilder() - .addFilter(documentationConfiguration(this.restDocumentation).operationPreprocessors() + .addFilter(documentationConfiguration(restDocumentation).operationPreprocessors() .withRequestDefaults(modifyHeaders().remove("Foo")) // <1> .withResponseDefaults(prettyPrint())) // <2> .build(); diff --git a/docs/src/test/java/com/example/restassured/CustomDefaultSnippets.java b/docs/src/test/java/com/example/restassured/CustomDefaultSnippets.java index 90ab1908..436d7d1a 100644 --- a/docs/src/test/java/com/example/restassured/CustomDefaultSnippets.java +++ b/docs/src/test/java/com/example/restassured/CustomDefaultSnippets.java @@ -18,27 +18,26 @@ import io.restassured.builder.RequestSpecBuilder; import io.restassured.specification.RequestSpecification; -import org.junit.Before; -import org.junit.Rule; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.extension.ExtendWith; -import org.springframework.restdocs.JUnitRestDocumentation; +import org.springframework.restdocs.RestDocumentationContextProvider; +import org.springframework.restdocs.RestDocumentationExtension; import static org.springframework.restdocs.cli.CliDocumentation.curlRequest; import static org.springframework.restdocs.restassured.RestAssuredRestDocumentation.documentationConfiguration; -public class CustomDefaultSnippets { - - @Rule - public final JUnitRestDocumentation restDocumentation = new JUnitRestDocumentation(); +@ExtendWith(RestDocumentationExtension.class) +class CustomDefaultSnippets { @SuppressWarnings("unused") private RequestSpecification spec; - @Before - public void setUp() { + @BeforeEach + void setUp(RestDocumentationContextProvider restDocumentation) { // tag::custom-default-snippets[] this.spec = new RequestSpecBuilder() - .addFilter(documentationConfiguration(this.restDocumentation).snippets().withDefaults(curlRequest())) + .addFilter(documentationConfiguration(restDocumentation).snippets().withDefaults(curlRequest())) .build(); // end::custom-default-snippets[] } diff --git a/docs/src/test/java/com/example/restassured/CustomEncoding.java b/docs/src/test/java/com/example/restassured/CustomEncoding.java index 2155fae6..0b130915 100644 --- a/docs/src/test/java/com/example/restassured/CustomEncoding.java +++ b/docs/src/test/java/com/example/restassured/CustomEncoding.java @@ -18,26 +18,25 @@ import io.restassured.builder.RequestSpecBuilder; import io.restassured.specification.RequestSpecification; -import org.junit.Before; -import org.junit.Rule; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.extension.ExtendWith; -import org.springframework.restdocs.JUnitRestDocumentation; +import org.springframework.restdocs.RestDocumentationContextProvider; +import org.springframework.restdocs.RestDocumentationExtension; import static org.springframework.restdocs.restassured.RestAssuredRestDocumentation.documentationConfiguration; -public class CustomEncoding { - - @Rule - public final JUnitRestDocumentation restDocumentation = new JUnitRestDocumentation(); +@ExtendWith(RestDocumentationExtension.class) +class CustomEncoding { @SuppressWarnings("unused") private RequestSpecification spec; - @Before - public void setUp() { + @BeforeEach + void setUp(RestDocumentationContextProvider restDocumentation) { // tag::custom-encoding[] this.spec = new RequestSpecBuilder() - .addFilter(documentationConfiguration(this.restDocumentation).snippets().withEncoding("ISO-8859-1")) + .addFilter(documentationConfiguration(restDocumentation).snippets().withEncoding("ISO-8859-1")) .build(); // end::custom-encoding[] } diff --git a/docs/src/test/java/com/example/restassured/CustomFormat.java b/docs/src/test/java/com/example/restassured/CustomFormat.java index 4fe9c95b..a47345e6 100644 --- a/docs/src/test/java/com/example/restassured/CustomFormat.java +++ b/docs/src/test/java/com/example/restassured/CustomFormat.java @@ -18,27 +18,26 @@ import io.restassured.builder.RequestSpecBuilder; import io.restassured.specification.RequestSpecification; -import org.junit.Before; -import org.junit.Rule; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.extension.ExtendWith; -import org.springframework.restdocs.JUnitRestDocumentation; +import org.springframework.restdocs.RestDocumentationContextProvider; +import org.springframework.restdocs.RestDocumentationExtension; import org.springframework.restdocs.templates.TemplateFormats; import static org.springframework.restdocs.restassured.RestAssuredRestDocumentation.documentationConfiguration; -public class CustomFormat { - - @Rule - public final JUnitRestDocumentation restDocumentation = new JUnitRestDocumentation(); +@ExtendWith(RestDocumentationExtension.class) +class CustomFormat { @SuppressWarnings("unused") private RequestSpecification spec; - @Before - public void setUp() { + @BeforeEach + void setUp(RestDocumentationContextProvider restDocumentation) { // tag::custom-format[] this.spec = new RequestSpecBuilder() - .addFilter(documentationConfiguration(this.restDocumentation).snippets() + .addFilter(documentationConfiguration(restDocumentation).snippets() .withTemplateFormat(TemplateFormats.markdown())) .build(); // end::custom-format[] diff --git a/docs/src/test/java/com/example/restassured/EveryTestPreprocessing.java b/docs/src/test/java/com/example/restassured/EveryTestPreprocessing.java index 441ce939..eceeecc8 100644 --- a/docs/src/test/java/com/example/restassured/EveryTestPreprocessing.java +++ b/docs/src/test/java/com/example/restassured/EveryTestPreprocessing.java @@ -19,10 +19,11 @@ import io.restassured.RestAssured; import io.restassured.builder.RequestSpecBuilder; import io.restassured.specification.RequestSpecification; -import org.junit.Before; -import org.junit.Rule; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.extension.ExtendWith; -import org.springframework.restdocs.JUnitRestDocumentation; +import org.springframework.restdocs.RestDocumentationContextProvider; +import org.springframework.restdocs.RestDocumentationExtension; import static org.hamcrest.CoreMatchers.is; import static org.springframework.restdocs.hypermedia.HypermediaDocumentation.linkWithRel; @@ -32,25 +33,23 @@ import static org.springframework.restdocs.restassured.RestAssuredRestDocumentation.document; import static org.springframework.restdocs.restassured.RestAssuredRestDocumentation.documentationConfiguration; -public class EveryTestPreprocessing { - - @Rule - public final JUnitRestDocumentation restDocumentation = new JUnitRestDocumentation(); +@ExtendWith(RestDocumentationExtension.class) +class EveryTestPreprocessing { // tag::setup[] private RequestSpecification spec; - @Before - public void setup() { + @BeforeEach + void setup(RestDocumentationContextProvider restDocumentation) { this.spec = new RequestSpecBuilder() - .addFilter(documentationConfiguration(this.restDocumentation).operationPreprocessors() + .addFilter(documentationConfiguration(restDocumentation).operationPreprocessors() .withRequestDefaults(modifyHeaders().remove("Foo")) // <1> .withResponseDefaults(prettyPrint())) // <2> .build(); } // end::setup[] - public void use() { + void use() { // tag::use[] RestAssured.given(this.spec) .filter(document("index", links(linkWithRel("self").description("Canonical self link")))) diff --git a/docs/src/test/java/com/example/restassured/ExampleApplicationJUnit5Tests.java b/docs/src/test/java/com/example/restassured/ExampleApplicationJUnit5Tests.java deleted file mode 100644 index 6398fbe1..00000000 --- a/docs/src/test/java/com/example/restassured/ExampleApplicationJUnit5Tests.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright 2014-present 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 com.example.restassured; - -import io.restassured.builder.RequestSpecBuilder; -import io.restassured.specification.RequestSpecification; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.extension.ExtendWith; - -import org.springframework.restdocs.RestDocumentationContextProvider; -import org.springframework.restdocs.RestDocumentationExtension; - -import static org.springframework.restdocs.restassured.RestAssuredRestDocumentation.documentationConfiguration; - -@ExtendWith(RestDocumentationExtension.class) -class ExampleApplicationJUnit5Tests { - - @SuppressWarnings("unused") - // tag::setup[] - private RequestSpecification spec; - - @BeforeEach - void setUp(RestDocumentationContextProvider restDocumentation) { - this.spec = new RequestSpecBuilder().addFilter(documentationConfiguration(restDocumentation)) // <1> - .build(); - } - // end::setup[] - -} diff --git a/docs/src/test/java/com/example/restassured/ExampleApplicationTests.java b/docs/src/test/java/com/example/restassured/ExampleApplicationTests.java index bcfdd561..f067e3b2 100644 --- a/docs/src/test/java/com/example/restassured/ExampleApplicationTests.java +++ b/docs/src/test/java/com/example/restassured/ExampleApplicationTests.java @@ -18,25 +18,24 @@ import io.restassured.builder.RequestSpecBuilder; import io.restassured.specification.RequestSpecification; -import org.junit.Before; -import org.junit.Rule; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.extension.ExtendWith; -import org.springframework.restdocs.JUnitRestDocumentation; +import org.springframework.restdocs.RestDocumentationContextProvider; +import org.springframework.restdocs.RestDocumentationExtension; import static org.springframework.restdocs.restassured.RestAssuredRestDocumentation.documentationConfiguration; -public class ExampleApplicationTests { - - @Rule - public final JUnitRestDocumentation restDocumentation = new JUnitRestDocumentation(); +@ExtendWith(RestDocumentationExtension.class) +class ExampleApplicationTests { @SuppressWarnings("unused") // tag::setup[] private RequestSpecification spec; - @Before - public void setUp() { - this.spec = new RequestSpecBuilder().addFilter(documentationConfiguration(this.restDocumentation)) // <1> + @BeforeEach + void setUp(RestDocumentationContextProvider restDocumentation) { + this.spec = new RequestSpecBuilder().addFilter(documentationConfiguration(restDocumentation)) // <1> .build(); } // end::setup[] diff --git a/docs/src/test/java/com/example/restassured/ParameterizedOutput.java b/docs/src/test/java/com/example/restassured/ParameterizedOutput.java index 9325f903..350a70c6 100644 --- a/docs/src/test/java/com/example/restassured/ParameterizedOutput.java +++ b/docs/src/test/java/com/example/restassured/ParameterizedOutput.java @@ -18,26 +18,25 @@ import io.restassured.builder.RequestSpecBuilder; import io.restassured.specification.RequestSpecification; -import org.junit.Before; -import org.junit.Rule; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.extension.ExtendWith; -import org.springframework.restdocs.JUnitRestDocumentation; +import org.springframework.restdocs.RestDocumentationContextProvider; +import org.springframework.restdocs.RestDocumentationExtension; import static org.springframework.restdocs.restassured.RestAssuredRestDocumentation.document; import static org.springframework.restdocs.restassured.RestAssuredRestDocumentation.documentationConfiguration; +@ExtendWith(RestDocumentationExtension.class) public class ParameterizedOutput { - @Rule - public final JUnitRestDocumentation restDocumentation = new JUnitRestDocumentation(); - @SuppressWarnings("unused") private RequestSpecification spec; // tag::parameterized-output[] - @Before - public void setUp() { - this.spec = new RequestSpecBuilder().addFilter(documentationConfiguration(this.restDocumentation)) + @BeforeEach + public void setUp(RestDocumentationContextProvider restDocumentation) { + this.spec = new RequestSpecBuilder().addFilter(documentationConfiguration(restDocumentation)) .addFilter(document("{method-name}/{step}")) .build(); } diff --git a/docs/src/test/java/com/example/webtestclient/CustomDefaultOperationPreprocessors.java b/docs/src/test/java/com/example/webtestclient/CustomDefaultOperationPreprocessors.java index cfebc822..8181d9be 100644 --- a/docs/src/test/java/com/example/webtestclient/CustomDefaultOperationPreprocessors.java +++ b/docs/src/test/java/com/example/webtestclient/CustomDefaultOperationPreprocessors.java @@ -16,35 +16,34 @@ package com.example.webtestclient; -import org.junit.Before; -import org.junit.Rule; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.context.ApplicationContext; -import org.springframework.restdocs.JUnitRestDocumentation; +import org.springframework.restdocs.RestDocumentationContextProvider; +import org.springframework.restdocs.RestDocumentationExtension; import org.springframework.test.web.reactive.server.WebTestClient; import static org.springframework.restdocs.operation.preprocess.Preprocessors.modifyHeaders; import static org.springframework.restdocs.operation.preprocess.Preprocessors.prettyPrint; import static org.springframework.restdocs.webtestclient.WebTestClientRestDocumentation.documentationConfiguration; -public class CustomDefaultOperationPreprocessors { +@ExtendWith(RestDocumentationExtension.class) +class CustomDefaultOperationPreprocessors { // @formatter:off - @Rule - public final JUnitRestDocumentation restDocumentation = new JUnitRestDocumentation(); - private ApplicationContext context; @SuppressWarnings("unused") private WebTestClient webTestClient; - @Before - public void setup() { + @BeforeEach + void setup(RestDocumentationContextProvider restDocumentation) { // tag::custom-default-operation-preprocessors[] this.webTestClient = WebTestClient.bindToApplicationContext(this.context) .configureClient() - .filter(documentationConfiguration(this.restDocumentation) + .filter(documentationConfiguration(restDocumentation) .operationPreprocessors() .withRequestDefaults(modifyHeaders().remove("Foo")) // <1> .withResponseDefaults(prettyPrint())) // <2> diff --git a/docs/src/test/java/com/example/webtestclient/CustomDefaultSnippets.java b/docs/src/test/java/com/example/webtestclient/CustomDefaultSnippets.java index ad4a8f49..5e45a335 100644 --- a/docs/src/test/java/com/example/webtestclient/CustomDefaultSnippets.java +++ b/docs/src/test/java/com/example/webtestclient/CustomDefaultSnippets.java @@ -16,36 +16,35 @@ package com.example.webtestclient; -import org.junit.Before; -import org.junit.Rule; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext; -import org.springframework.restdocs.JUnitRestDocumentation; +import org.springframework.restdocs.RestDocumentationContextProvider; +import org.springframework.restdocs.RestDocumentationExtension; import org.springframework.test.web.reactive.server.WebTestClient; import static org.springframework.restdocs.cli.CliDocumentation.curlRequest; import static org.springframework.restdocs.webtestclient.WebTestClientRestDocumentation.documentationConfiguration; -public class CustomDefaultSnippets { +@ExtendWith(RestDocumentationExtension.class) +class CustomDefaultSnippets { // @formatter:off - @Rule - public final JUnitRestDocumentation restDocumentation = new JUnitRestDocumentation(); - @Autowired private ApplicationContext context; @SuppressWarnings("unused") private WebTestClient webTestClient; - @Before - public void setUp() { + @BeforeEach + void setUp(RestDocumentationContextProvider restDocumentation) { // tag::custom-default-snippets[] this.webTestClient = WebTestClient.bindToApplicationContext(this.context) .configureClient().filter( - documentationConfiguration(this.restDocumentation) + documentationConfiguration(restDocumentation) .snippets().withDefaults(curlRequest())) .build(); // end::custom-default-snippets[] diff --git a/docs/src/test/java/com/example/webtestclient/CustomEncoding.java b/docs/src/test/java/com/example/webtestclient/CustomEncoding.java index dccf1c06..979996a9 100644 --- a/docs/src/test/java/com/example/webtestclient/CustomEncoding.java +++ b/docs/src/test/java/com/example/webtestclient/CustomEncoding.java @@ -16,34 +16,33 @@ package com.example.webtestclient; -import org.junit.Before; -import org.junit.Rule; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext; -import org.springframework.restdocs.JUnitRestDocumentation; +import org.springframework.restdocs.RestDocumentationContextProvider; +import org.springframework.restdocs.RestDocumentationExtension; import org.springframework.test.web.reactive.server.WebTestClient; import static org.springframework.restdocs.webtestclient.WebTestClientRestDocumentation.documentationConfiguration; -public class CustomEncoding { +@ExtendWith(RestDocumentationExtension.class) +class CustomEncoding { // @formatter:off - @Rule - public final JUnitRestDocumentation restDocumentation = new JUnitRestDocumentation(); - @Autowired private ApplicationContext context; @SuppressWarnings("unused") private WebTestClient webTestClient; - @Before - public void setUp() { + @BeforeEach + void setUp(RestDocumentationContextProvider restDocumentation) { // tag::custom-encoding[] this.webTestClient = WebTestClient.bindToApplicationContext(this.context).configureClient() - .filter(documentationConfiguration(this.restDocumentation) + .filter(documentationConfiguration(restDocumentation) .snippets().withEncoding("ISO-8859-1")) .build(); // end::custom-encoding[] diff --git a/docs/src/test/java/com/example/webtestclient/CustomFormat.java b/docs/src/test/java/com/example/webtestclient/CustomFormat.java index 1af44180..daa53aa7 100644 --- a/docs/src/test/java/com/example/webtestclient/CustomFormat.java +++ b/docs/src/test/java/com/example/webtestclient/CustomFormat.java @@ -16,35 +16,34 @@ package com.example.webtestclient; -import org.junit.Before; -import org.junit.Rule; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext; -import org.springframework.restdocs.JUnitRestDocumentation; +import org.springframework.restdocs.RestDocumentationContextProvider; +import org.springframework.restdocs.RestDocumentationExtension; import org.springframework.restdocs.templates.TemplateFormats; import org.springframework.test.web.reactive.server.WebTestClient; import static org.springframework.restdocs.webtestclient.WebTestClientRestDocumentation.documentationConfiguration; -public class CustomFormat { +@ExtendWith(RestDocumentationExtension.class) +class CustomFormat { // @formatter:off - @Rule - public final JUnitRestDocumentation restDocumentation = new JUnitRestDocumentation(); - @Autowired private ApplicationContext context; @SuppressWarnings("unused") private WebTestClient webTestClient; - @Before - public void setUp() { + @BeforeEach + void setUp(RestDocumentationContextProvider restDocumentation) { // tag::custom-format[] this.webTestClient = WebTestClient.bindToApplicationContext(this.context).configureClient() - .filter(documentationConfiguration(this.restDocumentation) + .filter(documentationConfiguration(restDocumentation) .snippets().withTemplateFormat(TemplateFormats.markdown())) .build(); // end::custom-format[] diff --git a/docs/src/test/java/com/example/webtestclient/CustomUriConfiguration.java b/docs/src/test/java/com/example/webtestclient/CustomUriConfiguration.java index 4016104f..ea0ffa90 100644 --- a/docs/src/test/java/com/example/webtestclient/CustomUriConfiguration.java +++ b/docs/src/test/java/com/example/webtestclient/CustomUriConfiguration.java @@ -16,20 +16,19 @@ package com.example.webtestclient; -import org.junit.Before; -import org.junit.Rule; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext; -import org.springframework.restdocs.JUnitRestDocumentation; +import org.springframework.restdocs.RestDocumentationContextProvider; +import org.springframework.restdocs.RestDocumentationExtension; import org.springframework.test.web.reactive.server.WebTestClient; import static org.springframework.restdocs.webtestclient.WebTestClientRestDocumentation.documentationConfiguration; -public class CustomUriConfiguration { - - @Rule - public final JUnitRestDocumentation restDocumentation = new JUnitRestDocumentation(); +@ExtendWith(RestDocumentationExtension.class) +class CustomUriConfiguration { @SuppressWarnings("unused") private WebTestClient webTestClient; @@ -38,12 +37,12 @@ public class CustomUriConfiguration { private ApplicationContext context; // tag::custom-uri-configuration[] - @Before - public void setUp() { + @BeforeEach + void setUp(RestDocumentationContextProvider restDocumentation) { this.webTestClient = WebTestClient.bindToApplicationContext(this.context) .configureClient() .baseUrl("https://api.example.com") // <1> - .filter(documentationConfiguration(this.restDocumentation)) + .filter(documentationConfiguration(restDocumentation)) .build(); } // end::custom-uri-configuration[] diff --git a/docs/src/test/java/com/example/webtestclient/EveryTestPreprocessing.java b/docs/src/test/java/com/example/webtestclient/EveryTestPreprocessing.java index 98c1e0aa..fa83f97f 100644 --- a/docs/src/test/java/com/example/webtestclient/EveryTestPreprocessing.java +++ b/docs/src/test/java/com/example/webtestclient/EveryTestPreprocessing.java @@ -16,11 +16,12 @@ package com.example.webtestclient; -import org.junit.Before; -import org.junit.Rule; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.context.ApplicationContext; -import org.springframework.restdocs.JUnitRestDocumentation; +import org.springframework.restdocs.RestDocumentationContextProvider; +import org.springframework.restdocs.RestDocumentationExtension; import org.springframework.test.web.reactive.server.WebTestClient; import static org.springframework.restdocs.hypermedia.HypermediaDocumentation.linkWithRel; @@ -30,23 +31,21 @@ import static org.springframework.restdocs.webtestclient.WebTestClientRestDocumentation.document; import static org.springframework.restdocs.webtestclient.WebTestClientRestDocumentation.documentationConfiguration; -public class EveryTestPreprocessing { +@ExtendWith(RestDocumentationExtension.class) +class EveryTestPreprocessing { // @formatter:off - @Rule - public final JUnitRestDocumentation restDocumentation = new JUnitRestDocumentation(); - private ApplicationContext context; // tag::setup[] private WebTestClient webTestClient; - @Before - public void setup() { + @BeforeEach + void setup(RestDocumentationContextProvider restDocumentation) { this.webTestClient = WebTestClient.bindToApplicationContext(this.context) .configureClient() - .filter(documentationConfiguration(this.restDocumentation) + .filter(documentationConfiguration(restDocumentation) .operationPreprocessors() .withRequestDefaults(modifyHeaders().remove("Foo")) // <1> .withResponseDefaults(prettyPrint())) // <2> @@ -54,7 +53,7 @@ public void setup() { } // end::setup[] - public void use() { + void use() { // tag::use[] this.webTestClient.get().uri("/").exchange().expectStatus().isOk() .expectBody().consumeWith(document("index", diff --git a/docs/src/test/java/com/example/webtestclient/ExampleApplicationJUnit5Tests.java b/docs/src/test/java/com/example/webtestclient/ExampleApplicationJUnit5Tests.java deleted file mode 100644 index c69ddabc..00000000 --- a/docs/src/test/java/com/example/webtestclient/ExampleApplicationJUnit5Tests.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright 2014-present 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 com.example.webtestclient; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.extension.ExtendWith; - -import org.springframework.context.ApplicationContext; -import org.springframework.restdocs.RestDocumentationContextProvider; -import org.springframework.restdocs.RestDocumentationExtension; -import org.springframework.test.web.reactive.server.WebTestClient; - -import static org.springframework.restdocs.webtestclient.WebTestClientRestDocumentation.documentationConfiguration; - -@ExtendWith(RestDocumentationExtension.class) -class ExampleApplicationJUnit5Tests { - - @SuppressWarnings("unused") - // tag::setup[] - private WebTestClient webTestClient; - - @BeforeEach - void setUp(ApplicationContext applicationContext, RestDocumentationContextProvider restDocumentation) { - this.webTestClient = WebTestClient.bindToApplicationContext(applicationContext) - .configureClient() - .filter(documentationConfiguration(restDocumentation)) // <1> - .build(); - } - // end::setup[] - -} diff --git a/docs/src/test/java/com/example/webtestclient/ExampleApplicationTests.java b/docs/src/test/java/com/example/webtestclient/ExampleApplicationTests.java index 8f85121f..ae0e084c 100644 --- a/docs/src/test/java/com/example/webtestclient/ExampleApplicationTests.java +++ b/docs/src/test/java/com/example/webtestclient/ExampleApplicationTests.java @@ -16,33 +16,28 @@ package com.example.webtestclient; -import org.junit.Before; -import org.junit.Rule; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.extension.ExtendWith; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext; -import org.springframework.restdocs.JUnitRestDocumentation; +import org.springframework.restdocs.RestDocumentationContextProvider; +import org.springframework.restdocs.RestDocumentationExtension; import org.springframework.test.web.reactive.server.WebTestClient; import static org.springframework.restdocs.webtestclient.WebTestClientRestDocumentation.documentationConfiguration; -public class ExampleApplicationTests { - - @Rule - public final JUnitRestDocumentation restDocumentation = new JUnitRestDocumentation(); +@ExtendWith(RestDocumentationExtension.class) +class ExampleApplicationTests { @SuppressWarnings("unused") // tag::setup[] private WebTestClient webTestClient; - @Autowired - private ApplicationContext context; - - @Before - public void setUp() { - this.webTestClient = WebTestClient.bindToApplicationContext(this.context) + @BeforeEach + void setUp(ApplicationContext applicationContext, RestDocumentationContextProvider restDocumentation) { + this.webTestClient = WebTestClient.bindToApplicationContext(applicationContext) .configureClient() - .filter(documentationConfiguration(this.restDocumentation)) // <1> + .filter(documentationConfiguration(restDocumentation)) // <1> .build(); } // end::setup[] diff --git a/docs/src/test/java/com/example/webtestclient/ParameterizedOutput.java b/docs/src/test/java/com/example/webtestclient/ParameterizedOutput.java index 105c0a54..79f93ae2 100644 --- a/docs/src/test/java/com/example/webtestclient/ParameterizedOutput.java +++ b/docs/src/test/java/com/example/webtestclient/ParameterizedOutput.java @@ -16,21 +16,20 @@ package com.example.webtestclient; -import org.junit.Before; -import org.junit.Rule; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext; -import org.springframework.restdocs.JUnitRestDocumentation; +import org.springframework.restdocs.RestDocumentationContextProvider; +import org.springframework.restdocs.RestDocumentationExtension; import org.springframework.test.web.reactive.server.WebTestClient; import static org.springframework.restdocs.webtestclient.WebTestClientRestDocumentation.document; import static org.springframework.restdocs.webtestclient.WebTestClientRestDocumentation.documentationConfiguration; -public class ParameterizedOutput { - - @Rule - public final JUnitRestDocumentation restDocumentation = new JUnitRestDocumentation(); +@ExtendWith(RestDocumentationExtension.class) +class ParameterizedOutput { @SuppressWarnings("unused") private WebTestClient webTestClient; @@ -39,11 +38,11 @@ public class ParameterizedOutput { private ApplicationContext context; // tag::parameterized-output[] - @Before - public void setUp() { + @BeforeEach + void setUp(RestDocumentationContextProvider restDocumentation) { this.webTestClient = WebTestClient.bindToApplicationContext(this.context) .configureClient() - .filter(documentationConfiguration(this.restDocumentation)) + .filter(documentationConfiguration(restDocumentation)) .entityExchangeResultConsumer(document("{method-name}/{step}")) .build(); } diff --git a/git/hooks/forward-merge b/git/hooks/forward-merge index acbb6008..a042bb46 100755 --- a/git/hooks/forward-merge +++ b/git/hooks/forward-merge @@ -18,7 +18,14 @@ class ForwardMerge end def find_forward_merges(message_file) + $log.debug "Searching for forward merge" + branch=`git rev-parse -q --abbrev-ref HEAD`.strip + $log.debug "Found #{branch} from git rev-parse --abbrev-ref" + if( branch == "docs-build") then + $log.debug "Skipping docs build" + return nil + end rev=`git rev-parse -q --verify MERGE_HEAD`.strip $log.debug "Found #{rev} from git rev-parse" return nil unless rev @@ -65,7 +72,7 @@ def find_milestone(username, password, repository, title) prefix = title.delete_suffix('.x') $log.debug "Finding nearest milestone from candidates starting with #{prefix}" titles = milestones.map { |milestone| milestone['title'] } - titles = titles.select{ |title| title.start_with?(prefix) unless title.end_with?('.x')} + titles = titles.select{ |title| title.start_with?(prefix) unless title.end_with?('.x') || (title.count('.') > 2)} titles = titles.sort_by { |v| Gem::Version.new(v) } $log.debug "Considering candidates #{titles}" if(titles.empty?) @@ -112,12 +119,16 @@ message_file=ARGV[0] forward_merges = find_forward_merges(message_file) exit 0 unless forward_merges -$log.debug "Loading config from ~/.spring-restdocs/forward_merge.yml" +$log.debug "Loading config from ~/.spring-restdocs/forward-merge.yml" config = YAML.load_file(File.join(Dir.home, '.spring-restdocs', 'forward-merge.yml')) username = config['github']['credentials']['username'] password = config['github']['credentials']['password'] dry_run = config['dry_run'] -repository = 'spring-projects/spring-restdocs' + +gradleProperties = IO.read('gradle.properties') +springBuildType = gradleProperties.match(/^spring\.build-type\s?=\s?(.*)$/) +repository = (springBuildType && springBuildType[1] != 'oss') ? "spring-projects/spring-restdocs-#{springBuildType[1]}" : "spring-projects/spring-restdocs"; +$log.debug "Targeting repository #{repository}" forward_merges.each do |forward_merge| existing_issue = get_issue(username, password, repository, forward_merge.issue) diff --git a/git/hooks/prepare-forward-merge b/git/hooks/prepare-forward-merge index 2b049790..fbdb1e19 100755 --- a/git/hooks/prepare-forward-merge +++ b/git/hooks/prepare-forward-merge @@ -4,7 +4,7 @@ require 'net/http' require 'yaml' require 'logger' -$main_branch = "3.0.x" +$main_branch = "4.0.x" $log = Logger.new(STDOUT) $log.level = Logger::WARN diff --git a/gradle.properties b/gradle.properties index d6f6c13a..8c6f81aa 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,9 +1,9 @@ -version=3.0.5-SNAPSHOT +version=4.0.0-SNAPSHOT org.gradle.caching=true org.gradle.jvmargs=-Xmx2g -Dfile.encoding=UTF-8 org.gradle.parallel=true javaFormatVersion=0.0.47 -jmustacheVersion=1.15 -springFrameworkVersion=6.2.7 +jmustacheVersion=1.16 +springFrameworkVersion=7.0.0-M7 diff --git a/gradle/plugins/build.gradle b/gradle/plugins/build.gradle new file mode 100644 index 00000000..a5a93955 --- /dev/null +++ b/gradle/plugins/build.gradle @@ -0,0 +1,32 @@ +plugins { + id "io.spring.javaformat" apply false + id "base" +} + +new File(rootDir.parentFile.parentFile, "gradle.properties").withInputStream { + def properties = new Properties() + properties.load(it) + properties.each { key, value -> + if (key.endsWith("Version")) { + project.ext.set(key, value) + } + } +} + +subprojects { + apply plugin: "io.spring.javaformat" + apply plugin: "checkstyle" + + repositories { + mavenCentral() + } + + checkstyle { + toolVersion = "10.12.4" + } + + dependencies { + checkstyle("io.spring.javaformat:spring-javaformat-checkstyle:$javaFormatVersion") + checkstyle("com.puppycrawl.tools:checkstyle:${checkstyle.toolVersion}") + } +} diff --git a/gradle/plugins/config/checkstyle/checkstyle.xml b/gradle/plugins/config/checkstyle/checkstyle.xml new file mode 100644 index 00000000..f8fd854f --- /dev/null +++ b/gradle/plugins/config/checkstyle/checkstyle.xml @@ -0,0 +1,7 @@ + + + + + \ No newline at end of file diff --git a/gradle/plugins/conventions/build.gradle b/gradle/plugins/conventions/build.gradle new file mode 100644 index 00000000..731c709c --- /dev/null +++ b/gradle/plugins/conventions/build.gradle @@ -0,0 +1,24 @@ +plugins { + id "java-gradle-plugin" +} + +repositories { + mavenCentral() + gradlePluginPortal() +} + +gradlePlugin { + plugins { + conventions { + id = "org.springframework.restdocs.conventions" + implementationClass = "org.springframework.restdocs.build.conventions.ConventionsPlugin" + } + } +} + +dependencies { + implementation(project(":toolchain")) + implementation("io.spring.gradle.nullability:nullability-plugin:0.0.2") + implementation("io.spring.javaformat:spring-javaformat-gradle-plugin:$javaFormatVersion") + implementation("io.spring.nohttp:nohttp-gradle:0.0.11") +} diff --git a/gradle/plugins/conventions/src/main/java/org/springframework/restdocs/build/conventions/Conventions.java b/gradle/plugins/conventions/src/main/java/org/springframework/restdocs/build/conventions/Conventions.java new file mode 100644 index 00000000..89f5d08b --- /dev/null +++ b/gradle/plugins/conventions/src/main/java/org/springframework/restdocs/build/conventions/Conventions.java @@ -0,0 +1,49 @@ +/* + * Copyright 2014-present 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.build.conventions; + +import org.gradle.api.Plugin; +import org.gradle.api.Project; + +/** + * Base class for {@link Plugin} conventions. + * + * @param the type of plugin to which the conventions apply + * @author Andy Wilkinson + */ +abstract class Conventions> { + + private final Project project; + + private final Class pluginType; + + Conventions(Project project, Class pluginType) { + this.project = project; + this.pluginType = pluginType; + } + + void apply() { + this.project.getPlugins().withType(this.pluginType).all(this::apply); + } + + abstract void apply(T plugin); + + protected Project getProject() { + return this.project; + } + +} diff --git a/gradle/plugins/conventions/src/main/java/org/springframework/restdocs/build/conventions/ConventionsPlugin.java b/gradle/plugins/conventions/src/main/java/org/springframework/restdocs/build/conventions/ConventionsPlugin.java new file mode 100644 index 00000000..dddae783 --- /dev/null +++ b/gradle/plugins/conventions/src/main/java/org/springframework/restdocs/build/conventions/ConventionsPlugin.java @@ -0,0 +1,37 @@ +/* + * Copyright 2014-present 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.build.conventions; + +import org.gradle.api.Plugin; +import org.gradle.api.Project; + +/** + * A {@link Plugin} that applies the Spring REST Docs project's Gradle build conventions. + * + * @author Andy Wilkinson + */ +public class ConventionsPlugin implements Plugin { + + @Override + public void apply(Project project) { + new JavaBasePluginConventions(project).apply(); + new MavenPublishPluginConventions(project).apply(); + new JavaTestFixturesPluginConventions(project).apply(); + new NoHttpConventions(project).apply(); + } + +} diff --git a/gradle/plugins/conventions/src/main/java/org/springframework/restdocs/build/conventions/JavaBasePluginConventions.java b/gradle/plugins/conventions/src/main/java/org/springframework/restdocs/build/conventions/JavaBasePluginConventions.java new file mode 100644 index 00000000..ad6de88c --- /dev/null +++ b/gradle/plugins/conventions/src/main/java/org/springframework/restdocs/build/conventions/JavaBasePluginConventions.java @@ -0,0 +1,137 @@ +/* + * Copyright 2014-present 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.build.conventions; + +import java.util.List; +import java.util.Map; + +import io.spring.gradle.nullability.NullabilityPlugin; +import io.spring.javaformat.gradle.SpringJavaFormatPlugin; +import org.gradle.api.JavaVersion; +import org.gradle.api.Project; +import org.gradle.api.artifacts.Configuration; +import org.gradle.api.artifacts.dsl.DependencyHandler; +import org.gradle.api.plugins.JavaBasePlugin; +import org.gradle.api.plugins.JavaPluginExtension; +import org.gradle.api.plugins.quality.CheckstyleExtension; +import org.gradle.api.plugins.quality.CheckstylePlugin; +import org.gradle.api.tasks.SourceSet; +import org.gradle.api.tasks.SourceSetContainer; +import org.gradle.api.tasks.compile.CompileOptions; +import org.gradle.api.tasks.compile.JavaCompile; +import org.gradle.api.tasks.testing.Test; + +import org.springframework.restdocs.build.toolchain.ToolchainPlugin; + +/** + * Conventions for the {@link JavaBasePlugin}. + * + * @author Andy Wilkinson + */ +class JavaBasePluginConventions extends Conventions { + + JavaBasePluginConventions(Project project) { + super(project, JavaBasePlugin.class); + } + + @Override + void apply(JavaBasePlugin plugin) { + configureNullability(); + configureToolchains(); + configureCheckstyle(); + configureJavaFormat(); + configureSourceAndTargetCompatibility(); + configureJavaCompileTasks(); + configureTestTasks(); + configureDependencyManagement(); + } + + private void configureNullability() { + getProject().getPlugins().apply(NullabilityPlugin.class); + } + + private void configureToolchains() { + getProject().getPlugins().apply(ToolchainPlugin.class); + } + + private void configureDependencyManagement() { + Configuration internal = getProject().getConfigurations().create("internal", (configuration) -> { + configuration.setCanBeConsumed(false); + configuration.setCanBeResolved(false); + DependencyHandler dependencies = getProject().getDependencies(); + configuration.getDependencies() + .add(dependencies.platform(dependencies.project(Map.of("path", ":spring-restdocs-platform")))); + }); + getProject().getExtensions().configure(JavaPluginExtension.class, (extension) -> { + SourceSetContainer sourceSets = extension.getSourceSets(); + sourceSets.all((sourceSet) -> configureDependencyManagement(sourceSet, internal)); + }); + } + + private void configureDependencyManagement(SourceSet sourceSet, Configuration internal) { + getProject().getConfigurations() + .getByName(sourceSet.getCompileClasspathConfigurationName()) + .extendsFrom(internal); + getProject().getConfigurations() + .getByName(sourceSet.getRuntimeClasspathConfigurationName()) + .extendsFrom(internal); + } + + private void configureSourceAndTargetCompatibility() { + getProject().getExtensions().configure(JavaPluginExtension.class, (extension) -> { + extension.setSourceCompatibility(JavaVersion.VERSION_17); + extension.setTargetCompatibility(JavaVersion.VERSION_17); + }); + } + + private void configureJavaCompileTasks() { + getProject().getTasks().withType(JavaCompile.class).configureEach((javaCompile) -> { + CompileOptions options = javaCompile.getOptions(); + options.setCompilerArgs(List.of("-Werror", "-Xlint:unchecked", "-Xlint:deprecation", "-Xlint:rawtypes", + "-Xlint:varargs", "-Xlint:options")); + options.setEncoding("UTF-8"); + options.getRelease().set(17); + }); + } + + private void configureTestTasks() { + getProject().getTasks().withType(Test.class).configureEach((test) -> { + test.setMaxHeapSize("1024M"); + test.useJUnitPlatform(); + }); + } + + private void configureCheckstyle() { + getProject().getPlugins().apply(CheckstylePlugin.class); + String checkstyleVersion = "10.12.4"; + getProject().getExtensions().configure(CheckstyleExtension.class, (checkstyle) -> { + Project rootProject = getProject().getRootProject(); + checkstyle.setConfigFile(rootProject.file("config/checkstyle/checkstyle.xml")); + checkstyle.setConfigProperties(Map.of("checkstyle.config.dir", rootProject.file("config/checkstyle"))); + checkstyle.setToolVersion(checkstyleVersion); + }); + getProject().getDependencies() + .add("checkstyle", "io.spring.javaformat:spring-javaformat-checkstyle:" + + SpringJavaFormatPlugin.class.getPackage().getImplementationVersion()); + getProject().getDependencies().add("checkstyle", "com.puppycrawl.tools:checkstyle:" + checkstyleVersion); + } + + private void configureJavaFormat() { + getProject().getPlugins().apply(SpringJavaFormatPlugin.class); + } + +} diff --git a/gradle/plugins/conventions/src/main/java/org/springframework/restdocs/build/conventions/JavaTestFixturesPluginConventions.java b/gradle/plugins/conventions/src/main/java/org/springframework/restdocs/build/conventions/JavaTestFixturesPluginConventions.java new file mode 100644 index 00000000..d1cca49e --- /dev/null +++ b/gradle/plugins/conventions/src/main/java/org/springframework/restdocs/build/conventions/JavaTestFixturesPluginConventions.java @@ -0,0 +1,46 @@ +/* + * Copyright 2014-present 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.build.conventions; + +import org.gradle.api.Project; +import org.gradle.api.artifacts.ConfigurationContainer; +import org.gradle.api.component.AdhocComponentWithVariants; +import org.gradle.api.plugins.JavaTestFixturesPlugin; + +/** + * Conventions for when the {@link JavaTestFixturesPlugin} is applied. + * + * @author Andy Wilkinson + */ +class JavaTestFixturesPluginConventions extends Conventions { + + JavaTestFixturesPluginConventions(Project project) { + super(project, JavaTestFixturesPlugin.class); + } + + @Override + void apply(JavaTestFixturesPlugin plugin) { + ConfigurationContainer configurations = getProject().getConfigurations(); + AdhocComponentWithVariants javaComponent = (AdhocComponentWithVariants) getProject().getComponents() + .getByName("java"); + javaComponent.withVariantsFromConfiguration(configurations.getByName("testFixturesApiElements"), + (variant) -> variant.skip()); + javaComponent.withVariantsFromConfiguration(configurations.getByName("testFixturesRuntimeElements"), + (variant) -> variant.skip()); + } + +} diff --git a/gradle/plugins/conventions/src/main/java/org/springframework/restdocs/build/conventions/MavenPublishPluginConventions.java b/gradle/plugins/conventions/src/main/java/org/springframework/restdocs/build/conventions/MavenPublishPluginConventions.java new file mode 100644 index 00000000..1bf26d0a --- /dev/null +++ b/gradle/plugins/conventions/src/main/java/org/springframework/restdocs/build/conventions/MavenPublishPluginConventions.java @@ -0,0 +1,132 @@ +/* + * Copyright 2014-present 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.build.conventions; + +import org.gradle.api.Project; +import org.gradle.api.component.SoftwareComponent; +import org.gradle.api.plugins.JavaPlatformPlugin; +import org.gradle.api.plugins.JavaPlugin; +import org.gradle.api.plugins.JavaPluginExtension; +import org.gradle.api.publish.PublishingExtension; +import org.gradle.api.publish.maven.MavenPublication; +import org.gradle.api.publish.maven.plugins.MavenPublishPlugin; +import org.gradle.api.publish.tasks.GenerateModuleMetadata; +import org.gradle.api.tasks.javadoc.Javadoc; +import org.gradle.external.javadoc.JavadocMemberLevel; +import org.gradle.external.javadoc.StandardJavadocDocletOptions; + +/** + * Conventions for when the {@link MavenPublishPlugin} is applied. + * + * @author Andy Wilkinson + */ +class MavenPublishPluginConventions extends Conventions { + + MavenPublishPluginConventions(Project project) { + super(project, MavenPublishPlugin.class); + } + + @Override + void apply(MavenPublishPlugin plugin) { + getProject().getTasks().withType(GenerateModuleMetadata.class).configureEach((task) -> task.setEnabled(false)); + PublishingExtension publishing = getProject().getExtensions().getByType(PublishingExtension.class); + configureDeploymentRepository(publishing); + publishing.publications((publications) -> publications.create("maven", MavenPublication.class, + this::configureMavenPublication)); + } + + private void configureMavenPublication(MavenPublication maven) { + configureContents(maven); + configurePom(maven); + } + + private void configureContents(MavenPublication maven) { + getProject().getPlugins().withType(JavaPlugin.class).configureEach((javaPlugin) -> { + SoftwareComponent java = getProject().getComponents().getByName("java"); + maven.from(java); + maven.versionMapping((versionMapping) -> { + versionMapping.usage("java-api", (strategy) -> strategy.fromResolutionResult()); + versionMapping.usage("java-runtime", (strategy) -> strategy.fromResolutionResult()); + }); + getProject().getExtensions().configure(JavaPluginExtension.class, (extension) -> { + extension.withSourcesJar(); + extension.withJavadocJar(); + }); + getProject().getTasks().withType(Javadoc.class).configureEach((javadoc) -> { + javadoc.setDescription("Generates project-level javadoc for use in -javadoc jar"); + StandardJavadocDocletOptions options = (StandardJavadocDocletOptions) javadoc.getOptions(); + options.setMemberLevel(JavadocMemberLevel.PROTECTED); + options.setAuthor(true); + options.header("Spring REST Docs " + getProject().getVersion()); + options.docTitle(options.getHeader() + " API"); + options.addStringOption("-quiet"); + options.encoding("UTF-8"); + options.source("17"); + options.links( + "https://docs.spring.io/spring-framework/docs/" + + getProject().property("springFrameworkVersion") + "/javadoc-api/", + "https://docs.jboss.org/hibernate/validator/9.0/api/", + "https://jakarta.ee/specifications/bean-validation/3.1/apidocs/"); + }); + }); + getProject().getPlugins() + .withType(JavaPlatformPlugin.class) + .configureEach((javaPlatformPlugin) -> maven.from(getProject().getComponents().getByName("javaPlatform"))); + } + + private void configureDeploymentRepository(PublishingExtension publishing) { + Object deploymentRepository = getProject().findProperty("deploymentRepository"); + if (deploymentRepository != null) { + publishing.getRepositories().maven((repository) -> { + repository.setName("deployment"); + repository.setUrl(deploymentRepository); + }); + } + } + + private void configurePom(MavenPublication maven) { + maven.pom((pom) -> { + pom.getName().set(getProject().provider(getProject()::getDescription)); + pom.getDescription().set(getProject().provider(getProject()::getDescription)); + pom.getUrl().set("https://github.com/spring-projects/spring-restdocs"); + pom.organization((organization) -> { + organization.getName().set("Spring IO"); + organization.getUrl().set("https://projects.spring.io/spring-restdocs"); + }); + pom.licenses((licenses) -> licenses.license((licence) -> { + licence.getName().set("The Apache Software License, Version 2.0"); + licence.getUrl().set("https://www.apache.org/licenses/LICENSE-2.0.txt"); + licence.getDistribution().set("repo"); + })); + pom.scm((scm) -> { + scm.getUrl().set("https://github.com/spring-projects/spring-restdocs"); + scm.getConnection().set("scm:git:git://github.com/spring-projects/spring-restdocs"); + scm.getDeveloperConnection().set("scm:git:git://github.com/spring-projects/spring-restdocs"); + }); + pom.developers((developers) -> developers.developer((developer) -> { + developer.getId().set("wilkinsona"); + developer.getName().set("Andy Wilkinson"); + developer.getEmail().set("andy.wilkinson@broadcom.com"); + })); + pom.issueManagement((issueManagement) -> { + issueManagement.getSystem().set("GitHub"); + issueManagement.getUrl().set("https://github.com/spring-projects/spring-restdocs/issues"); + }); + }); + } + +} diff --git a/gradle/plugins/conventions/src/main/java/org/springframework/restdocs/build/conventions/NoHttpConventions.java b/gradle/plugins/conventions/src/main/java/org/springframework/restdocs/build/conventions/NoHttpConventions.java new file mode 100644 index 00000000..05d750c3 --- /dev/null +++ b/gradle/plugins/conventions/src/main/java/org/springframework/restdocs/build/conventions/NoHttpConventions.java @@ -0,0 +1,56 @@ +/* + * Copyright 2014-present 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.build.conventions; + +import io.spring.nohttp.gradle.NoHttpCheckstylePlugin; +import io.spring.nohttp.gradle.NoHttpExtension; +import org.gradle.api.Project; +import org.gradle.api.file.ConfigurableFileTree; +import org.gradle.api.plugins.quality.Checkstyle; + +class NoHttpConventions { + + private final Project project; + + NoHttpConventions(Project project) { + this.project = project; + } + + void apply() { + if (this.project.equals(this.project.getRootProject())) { + return; + } + this.project.getPluginManager().apply(NoHttpCheckstylePlugin.class); + configureNoHttpExtension(this.project, this.project.getExtensions().getByType(NoHttpExtension.class)); + this.project.getTasks() + .named(NoHttpCheckstylePlugin.CHECKSTYLE_NOHTTP_TASK_NAME, Checkstyle.class) + .configure((task) -> task.getConfigDirectory().set(this.project.getRootProject().file("config/nohttp"))); + } + + private void configureNoHttpExtension(Project project, NoHttpExtension extension) { + ConfigurableFileTree source = extension.getSource(); + source.exclude("bin/**"); + source.exclude("build/**"); + source.exclude("out/**"); + source.exclude("target/**"); + source.exclude(".settings/**"); + source.exclude(".classpath"); + source.exclude(".project"); + source.exclude(".gradle"); + } + +} diff --git a/gradle/plugins/optional-dependencies/build.gradle b/gradle/plugins/optional-dependencies/build.gradle new file mode 100644 index 00000000..cd8a13bb --- /dev/null +++ b/gradle/plugins/optional-dependencies/build.gradle @@ -0,0 +1,12 @@ +plugins { + id "java-gradle-plugin" +} + +gradlePlugin { + plugins { + optionalDependencies { + id = "org.springframework.restdocs.optional-dependencies" + implementationClass = "org.springframework.restdocs.build.optional.OptionalDependenciesPlugin" + } + } +} \ No newline at end of file diff --git a/buildSrc/src/main/java/org/springframework/restdocs/build/optional/OptionalDependenciesPlugin.java b/gradle/plugins/optional-dependencies/src/main/java/org/springframework/restdocs/build/optional/OptionalDependenciesPlugin.java similarity index 83% rename from buildSrc/src/main/java/org/springframework/restdocs/build/optional/OptionalDependenciesPlugin.java rename to gradle/plugins/optional-dependencies/src/main/java/org/springframework/restdocs/build/optional/OptionalDependenciesPlugin.java index 308f84a8..61bffabc 100644 --- a/buildSrc/src/main/java/org/springframework/restdocs/build/optional/OptionalDependenciesPlugin.java +++ b/gradle/plugins/optional-dependencies/src/main/java/org/springframework/restdocs/build/optional/OptionalDependenciesPlugin.java @@ -46,24 +46,29 @@ public class OptionalDependenciesPlugin implements Plugin { public void apply(Project project) { Configuration optional = project.getConfigurations().create(OPTIONAL_CONFIGURATION_NAME); project.getConfigurations().all((configuration) -> { - if (configuration.getName().startsWith("testRuntimeClasspath_") || configuration.getName().startsWith("testCompileClasspath_")) { + if (configuration.getName().startsWith("testRuntimeClasspath_") + || configuration.getName().startsWith("testCompileClasspath_")) { configuration.extendsFrom(optional); } }); optional.attributes((attributes) -> attributes.attribute(Usage.USAGE_ATTRIBUTE, project.getObjects().named(Usage.class, Usage.JAVA_RUNTIME))); project.getPlugins().withType(JavaPlugin.class, (javaPlugin) -> { - SourceSetContainer sourceSets = project.getExtensions().getByType(JavaPluginExtension.class) - .getSourceSets(); + SourceSetContainer sourceSets = project.getExtensions() + .getByType(JavaPluginExtension.class) + .getSourceSets(); sourceSets.all((sourceSet) -> { sourceSet.setCompileClasspath(sourceSet.getCompileClasspath().plus(optional)); sourceSet.setRuntimeClasspath(sourceSet.getRuntimeClasspath().plus(optional)); }); - project.getTasks().withType(Javadoc.class) - .all((javadoc) -> javadoc.setClasspath(javadoc.getClasspath().plus(optional))); + project.getTasks() + .withType(Javadoc.class) + .all((javadoc) -> javadoc.setClasspath(javadoc.getClasspath().plus(optional))); }); - project.getPlugins().withType(EclipsePlugin.class, - (eclipsePlugin) -> project.getExtensions().getByType(EclipseModel.class) + project.getPlugins() + .withType(EclipsePlugin.class, + (eclipsePlugin) -> project.getExtensions() + .getByType(EclipseModel.class) .classpath((classpath) -> classpath.getPlusConfigurations().add(optional))); } diff --git a/buildSrc/src/main/resources/META-INF/gradle-plugins/optional-dependencies.properties b/gradle/plugins/optional-dependencies/src/main/resources/META-INF/gradle-plugins/optional-dependencies.properties similarity index 100% rename from buildSrc/src/main/resources/META-INF/gradle-plugins/optional-dependencies.properties rename to gradle/plugins/optional-dependencies/src/main/resources/META-INF/gradle-plugins/optional-dependencies.properties diff --git a/gradle/plugins/settings.gradle b/gradle/plugins/settings.gradle new file mode 100644 index 00000000..313bdf47 --- /dev/null +++ b/gradle/plugins/settings.gradle @@ -0,0 +1,18 @@ +pluginManagement { + new File(rootDir.parentFile.parentFile, "gradle.properties").withInputStream { + def properties = new Properties() + properties.load(it) + properties.each { key, value -> + if (key.endsWith("Version")) { + gradle.extensions.extraProperties.set(key, value) + } + } + } + plugins { + id "io.spring.javaformat" version gradle.extensions.extraProperties.get("javaFormatVersion") + } +} + +include "conventions" +include "optional-dependencies" +include "toolchain" diff --git a/gradle/plugins/toolchain/build.gradle b/gradle/plugins/toolchain/build.gradle new file mode 100644 index 00000000..19baf51f --- /dev/null +++ b/gradle/plugins/toolchain/build.gradle @@ -0,0 +1,12 @@ +plugins { + id "java-gradle-plugin" +} + +gradlePlugin { + plugins { + optionalDependencies { + id = "org.springframework.restdocs.toolchain" + implementationClass = "org.springframework.restdocs.build.toolchain.ToolchainPlugin" + } + } +} diff --git a/gradle/plugins/toolchain/src/main/java/org/springframework/restdocs/build/toolchain/ToolchainPlugin.java b/gradle/plugins/toolchain/src/main/java/org/springframework/restdocs/build/toolchain/ToolchainPlugin.java new file mode 100644 index 00000000..43ce76c2 --- /dev/null +++ b/gradle/plugins/toolchain/src/main/java/org/springframework/restdocs/build/toolchain/ToolchainPlugin.java @@ -0,0 +1,40 @@ +/* + * Copyright 2014-present 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.build.toolchain; + +import org.gradle.api.Plugin; +import org.gradle.api.Project; +import org.gradle.api.tasks.testing.Test; +import org.gradle.jvm.toolchain.JavaLanguageVersion; +import org.gradle.jvm.toolchain.JavaToolchainService; + +public class ToolchainPlugin implements Plugin { + + @Override + public void apply(Project project) { + String toolchainVersion = (String) project.findProperty("toolchainVersion"); + if (toolchainVersion != null) { + JavaToolchainService toolchainService = project.getExtensions().getByType(JavaToolchainService.class); + project.getTasks() + .withType(Test.class) + .configureEach((test) -> test.getJavaLauncher() + .set(toolchainService.launcherFor( + (spec) -> spec.getLanguageVersion().set(JavaLanguageVersion.of(toolchainVersion))))); + } + } + +} diff --git a/gradle/plugins/toolchain/src/main/java/org/springframework/restdocs/build/toolchain/package-info.java b/gradle/plugins/toolchain/src/main/java/org/springframework/restdocs/build/toolchain/package-info.java new file mode 100644 index 00000000..eccceda3 --- /dev/null +++ b/gradle/plugins/toolchain/src/main/java/org/springframework/restdocs/build/toolchain/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright 2014-present 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. + */ + +/** + * Toolchain plugin. + */ +package org.springframework.restdocs.build.toolchain; diff --git a/gradle/publish-maven.gradle b/gradle/publish-maven.gradle deleted file mode 100644 index 3c50ccb7..00000000 --- a/gradle/publish-maven.gradle +++ /dev/null @@ -1,62 +0,0 @@ -plugins.withType(MavenPublishPlugin) { - publishing { - if (project.hasProperty("deploymentRepository")) { - repositories { - maven { - url = project.property("deploymentRepository") - name = "deployment" - } - } - } - publications { - maven(MavenPublication) { publication -> - project.plugins.withType(JavaPlugin) { - from components.java - versionMapping { - usage("java-api") { - fromResolutionResult() - } - usage("java-runtime") { - fromResolutionResult() - } - } - } - project.plugins.withType(JavaPlatformPlugin) { - from components.javaPlatform - } - pom { - name = project.provider { project.description } - description = project.provider { project.description } - url = "https://github.com/spring-projects/spring-restdocs" - organization { - name = "Spring IO" - url = "https://projects.spring.io/spring-restdocs" - } - licenses { - license { - name = "The Apache Software License, Version 2.0" - url = "https://www.apache.org/licenses/LICENSE-2.0.txt" - distribution = "repo" - } - } - scm { - url = "https://github.com/spring-projects/spring-restdocs" - connection = "scm:git:git://github.com/spring-projects/spring-restdocs" - developerConnection = "scm:git:git://github.com/spring-projects/spring-restdocs" - } - developers { - developer { - id = "awilkinson" - name = "Andy Wilkinson" - email = "awilkinson@pivotal.io" - } - } - issueManagement { - system = "GitHub" - url = "https://github.com/spring-projects/spring-restdocs/issues" - } - } - } - } - } -} diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 002b867c..d4081da4 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/settings.gradle b/settings.gradle index c76a3785..10fd4b21 100644 --- a/settings.gradle +++ b/settings.gradle @@ -2,15 +2,8 @@ pluginManagement { repositories { mavenCentral() gradlePluginPortal() - maven { url = 'https://repo.spring.io/snapshot' } - } - resolutionStrategy { - eachPlugin { - if (requested.id.id == "io.spring.javaformat") { - useModule "io.spring.javaformat:spring-javaformat-gradle-plugin:${requested.version}" - } - } } + includeBuild "gradle/plugins" } plugins { diff --git a/spring-restdocs-asciidoctor/build.gradle b/spring-restdocs-asciidoctor/build.gradle index 2ab2e653..5d6ae600 100644 --- a/spring-restdocs-asciidoctor/build.gradle +++ b/spring-restdocs-asciidoctor/build.gradle @@ -1,30 +1,21 @@ plugins { + id 'org.springframework.restdocs.conventions' id "java-library" id "maven-publish" - id "io.spring.compatibility-test" version "0.0.4" } description = "Spring REST Docs Asciidoctor Extension" dependencies { - implementation("org.asciidoctor:asciidoctorj") + compileOnly("org.jspecify:jspecify") - internal(platform(project(":spring-restdocs-platform"))) + implementation("org.asciidoctor:asciidoctorj") - testImplementation("junit:junit") - testImplementation("org.apache.pdfbox:pdfbox") { - exclude group: "commons-logging", module: "commons-logging" - } + testImplementation("org.apache.pdfbox:pdfbox") testImplementation("org.assertj:assertj-core") + testImplementation("org.junit.jupiter:junit-jupiter") testImplementation("org.springframework:spring-core") testRuntimeOnly("org.asciidoctor:asciidoctorj-pdf") + testRuntimeOnly("org.junit.platform:junit-platform-launcher") } - -compatibilityTest { - dependency("AsciidoctorJ") { asciidoctorj -> - asciidoctorj.groupId = "org.asciidoctor" - asciidoctorj.artifactId = "asciidoctorj" - asciidoctorj.versions = ["3.0.0"] - } -} \ No newline at end of file diff --git a/spring-restdocs-asciidoctor/src/main/java/org/springframework/restdocs/asciidoctor/DefaultAttributesPreprocessor.java b/spring-restdocs-asciidoctor/src/main/java/org/springframework/restdocs/asciidoctor/DefaultAttributesPreprocessor.java new file mode 100644 index 00000000..17aa7770 --- /dev/null +++ b/spring-restdocs-asciidoctor/src/main/java/org/springframework/restdocs/asciidoctor/DefaultAttributesPreprocessor.java @@ -0,0 +1,41 @@ +/* + * Copyright 2014-present 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.asciidoctor; + +import org.asciidoctor.ast.Document; +import org.asciidoctor.extension.Preprocessor; +import org.asciidoctor.extension.PreprocessorReader; +import org.asciidoctor.extension.Reader; + +/** + * {@link Preprocessor} that sets defaults for REST Docs-related {@link Document} + * attributes. + * + * @author Andy Wilkinson + */ +final class DefaultAttributesPreprocessor extends Preprocessor { + + private final SnippetsDirectoryResolver snippetsDirectoryResolver = new SnippetsDirectoryResolver(); + + @Override + public Reader process(Document document, PreprocessorReader reader) { + document.setAttribute("snippets", this.snippetsDirectoryResolver.getSnippetsDirectory(document.getAttributes()), + false); + return reader; + } + +} diff --git a/spring-restdocs-asciidoctor/src/main/java/org/springframework/restdocs/asciidoctor/RestDocsExtensionRegistry.java b/spring-restdocs-asciidoctor/src/main/java/org/springframework/restdocs/asciidoctor/RestDocsExtensionRegistry.java index 94bb7d5f..699902bf 100644 --- a/spring-restdocs-asciidoctor/src/main/java/org/springframework/restdocs/asciidoctor/RestDocsExtensionRegistry.java +++ b/spring-restdocs-asciidoctor/src/main/java/org/springframework/restdocs/asciidoctor/RestDocsExtensionRegistry.java @@ -28,9 +28,7 @@ public final class RestDocsExtensionRegistry implements ExtensionRegistry { @Override public void register(Asciidoctor asciidoctor) { - asciidoctor.rubyExtensionRegistry() - .loadClass(RestDocsExtensionRegistry.class.getResourceAsStream("/extensions/default_attributes.rb")) - .preprocessor("DefaultAttributes"); + asciidoctor.javaExtensionRegistry().preprocessor(new DefaultAttributesPreprocessor()); asciidoctor.rubyExtensionRegistry() .loadClass(RestDocsExtensionRegistry.class.getResourceAsStream("/extensions/operation_block_macro.rb")) .blockMacro("operation", "OperationBlockMacro"); diff --git a/spring-restdocs-asciidoctor/src/main/java/org/springframework/restdocs/asciidoctor/SnippetsDirectoryResolver.java b/spring-restdocs-asciidoctor/src/main/java/org/springframework/restdocs/asciidoctor/SnippetsDirectoryResolver.java index 2e9eb879..b3e8760a 100644 --- a/spring-restdocs-asciidoctor/src/main/java/org/springframework/restdocs/asciidoctor/SnippetsDirectoryResolver.java +++ b/spring-restdocs-asciidoctor/src/main/java/org/springframework/restdocs/asciidoctor/SnippetsDirectoryResolver.java @@ -23,20 +23,17 @@ import java.util.Map; import java.util.function.Supplier; +import org.jspecify.annotations.Nullable; + /** * Resolves the directory from which snippets can be read for inclusion in an Asciidoctor * document. The resolved directory is absolute. * * @author Andy Wilkinson */ -public class SnippetsDirectoryResolver { +class SnippetsDirectoryResolver { - /** - * Returns the snippets directory derived from the given {@code attributes}. - * @param attributes the attributes - * @return the snippets directory - */ - public File getSnippetsDirectory(Map attributes) { + File getSnippetsDirectory(Map attributes) { if (System.getProperty("maven.home") != null) { return getMavenSnippetsDirectory(attributes); } @@ -45,7 +42,12 @@ public File getSnippetsDirectory(Map attributes) { private File getMavenSnippetsDirectory(Map attributes) { Path docdir = Paths.get(getRequiredAttribute(attributes, "docdir")); - return new File(findPom(docdir).getParent().toFile(), "target/generated-snippets").getAbsoluteFile(); + Path pom = findPom(docdir); + Path parent = pom.getParent(); + if (parent == null) { + throw new IllegalStateException("Pom '" + pom + "' has no parent directory"); + } + return new File(parent.toFile(), "target/generated-snippets").getAbsoluteFile(); } private Path findPom(Path docdir) { @@ -70,7 +72,8 @@ private String getRequiredAttribute(Map attributes, String name) return getRequiredAttribute(attributes, name, null); } - private String getRequiredAttribute(Map attributes, String name, Supplier fallback) { + private String getRequiredAttribute(Map attributes, String name, + @Nullable Supplier fallback) { String attribute = (String) attributes.get(name); if (attribute == null || attribute.length() == 0) { if (fallback != null) { diff --git a/spring-restdocs-asciidoctor/src/main/java/org/springframework/restdocs/asciidoctor/package-info.java b/spring-restdocs-asciidoctor/src/main/java/org/springframework/restdocs/asciidoctor/package-info.java index 15f40593..2733fef7 100644 --- a/spring-restdocs-asciidoctor/src/main/java/org/springframework/restdocs/asciidoctor/package-info.java +++ b/spring-restdocs-asciidoctor/src/main/java/org/springframework/restdocs/asciidoctor/package-info.java @@ -17,4 +17,7 @@ /** * Support for Asciidoctor. */ +@NullMarked package org.springframework.restdocs.asciidoctor; + +import org.jspecify.annotations.NullMarked; diff --git a/spring-restdocs-asciidoctor/src/main/resources/extensions/default_attributes.rb b/spring-restdocs-asciidoctor/src/main/resources/extensions/default_attributes.rb deleted file mode 100644 index a4060d5b..00000000 --- a/spring-restdocs-asciidoctor/src/main/resources/extensions/default_attributes.rb +++ /dev/null @@ -1,14 +0,0 @@ -require 'asciidoctor/extensions' -require 'java' - -class DefaultAttributes < Asciidoctor::Extensions::Preprocessor - - def process(document, reader) - resolver = org.springframework.restdocs.asciidoctor.SnippetsDirectoryResolver.new() - attributes = document.attributes - attributes["snippets"] = resolver.getSnippetsDirectory(attributes) unless attributes.has_key?("snippets") - false - end - -end - \ No newline at end of file diff --git a/spring-restdocs-asciidoctor/src/test/java/org/springframework/restdocs/asciidoctor/AbstractOperationBlockMacroTests.java b/spring-restdocs-asciidoctor/src/test/java/org/springframework/restdocs/asciidoctor/AbstractOperationBlockMacroTests.java index 831cdc88..e5384466 100644 --- a/spring-restdocs-asciidoctor/src/test/java/org/springframework/restdocs/asciidoctor/AbstractOperationBlockMacroTests.java +++ b/spring-restdocs-asciidoctor/src/test/java/org/springframework/restdocs/asciidoctor/AbstractOperationBlockMacroTests.java @@ -26,6 +26,7 @@ import java.util.ArrayList; import java.util.List; +import org.apache.pdfbox.Loader; import org.apache.pdfbox.contentstream.PDFStreamEngine; import org.apache.pdfbox.contentstream.operator.Operator; import org.apache.pdfbox.cos.COSBase; @@ -35,11 +36,10 @@ import org.asciidoctor.Attributes; import org.asciidoctor.Options; import org.asciidoctor.SafeMode; -import org.junit.After; -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.TemporaryFolder; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; import org.springframework.util.FileSystemUtils; @@ -51,42 +51,42 @@ * @author Gerrit Meier * @author Andy Wilkinson */ -public abstract class AbstractOperationBlockMacroTests { +abstract class AbstractOperationBlockMacroTests { - @Rule - public TemporaryFolder temp = new TemporaryFolder(); + private final Asciidoctor asciidoctor = Asciidoctor.Factory.create(); - private Options options; + @TempDir + protected File temp; - private final Asciidoctor asciidoctor = Asciidoctor.Factory.create(); + private Options options; - @Before - public void setUp() throws IOException { + @BeforeEach + void setUp() throws IOException { prepareOperationSnippets(getBuildOutputLocation()); this.options = Options.builder().safe(SafeMode.UNSAFE).baseDir(getSourceLocation()).build(); this.options.setAttributes(getAttributes()); CapturingLogHandler.clear(); } - @After - public void verifyLogging() { + @AfterEach + void verifyLogging() { assertThat(CapturingLogHandler.getLogRecords()).isEmpty(); } - public void prepareOperationSnippets(File buildOutputLocation) throws IOException { + private void prepareOperationSnippets(File buildOutputLocation) throws IOException { File destination = new File(buildOutputLocation, "generated-snippets/some-operation"); destination.mkdirs(); FileSystemUtils.copyRecursively(new File("src/test/resources/some-operation"), destination); } @Test - public void codeBlockSnippetInclude() throws Exception { + void codeBlockSnippetInclude() throws Exception { String result = this.asciidoctor.convert("operation::some-operation[snippets='curl-request']", this.options); assertThat(result).isEqualTo(getExpectedContentFromFile("snippet-simple")); } @Test - public void operationWithParameterizedName() throws Exception { + void operationWithParameterizedName() throws Exception { Attributes attributes = getAttributes(); attributes.setAttribute("name", "some"); this.options.setAttributes(attributes); @@ -95,20 +95,20 @@ public void operationWithParameterizedName() throws Exception { } @Test - public void codeBlockSnippetIncludeWithPdfBackend() throws Exception { + void codeBlockSnippetIncludeWithPdfBackend() throws Exception { File output = configurePdfOutput(); this.asciidoctor.convert("operation::some-operation[snippets='curl-request']", this.options); assertThat(extractStrings(output)).containsExactly("Curl request", "$ curl 'http://localhost:8080/' -i", "1"); } @Test - public void tableSnippetInclude() throws Exception { + void tableSnippetInclude() throws Exception { String result = this.asciidoctor.convert("operation::some-operation[snippets='response-fields']", this.options); assertThat(result).isEqualTo(getExpectedContentFromFile("snippet-table")); } @Test - public void tableSnippetIncludeWithPdfBackend() throws Exception { + void tableSnippetIncludeWithPdfBackend() throws Exception { File output = configurePdfOutput(); this.asciidoctor.convert("operation::some-operation[snippets='response-fields']", this.options); assertThat(extractStrings(output)).containsExactly("Response fields", "Path", "Type", "Description", "a", @@ -116,14 +116,14 @@ public void tableSnippetIncludeWithPdfBackend() throws Exception { } @Test - public void includeSnippetInSection() throws Exception { + void includeSnippetInSection() throws Exception { String result = this.asciidoctor.convert("= A\n:doctype: book\n:sectnums:\n\nAlpha\n\n== B\n\nBravo\n\n" + "operation::some-operation[snippets='curl-request']\n\n== C\n", this.options); assertThat(result).isEqualTo(getExpectedContentFromFile("snippet-in-section")); } @Test - public void includeSnippetInSectionWithAbsoluteLevelOffset() throws Exception { + void includeSnippetInSectionWithAbsoluteLevelOffset() throws Exception { String result = this.asciidoctor .convert("= A\n:doctype: book\n:sectnums:\n:leveloffset: 1\n\nAlpha\n\n= B\n\nBravo\n\n" + "operation::some-operation[snippets='curl-request']\n\n= C\n", this.options); @@ -131,7 +131,7 @@ public void includeSnippetInSectionWithAbsoluteLevelOffset() throws Exception { } @Test - public void includeSnippetInSectionWithRelativeLevelOffset() throws Exception { + void includeSnippetInSectionWithRelativeLevelOffset() throws Exception { String result = this.asciidoctor .convert("= A\n:doctype: book\n:sectnums:\n:leveloffset: +1\n\nAlpha\n\n= B\n\nBravo\n\n" + "operation::some-operation[snippets='curl-request']\n\n= C\n", this.options); @@ -139,7 +139,7 @@ public void includeSnippetInSectionWithRelativeLevelOffset() throws Exception { } @Test - public void includeSnippetInSectionWithPdfBackend() throws Exception { + void includeSnippetInSectionWithPdfBackend() throws Exception { File output = configurePdfOutput(); this.asciidoctor.convert("== Section\n" + "operation::some-operation[snippets='curl-request']", this.options); assertThat(extractStrings(output)).containsExactly("Section", "Curl request", @@ -147,26 +147,26 @@ public void includeSnippetInSectionWithPdfBackend() throws Exception { } @Test - public void includeMultipleSnippets() throws Exception { + void includeMultipleSnippets() throws Exception { String result = this.asciidoctor.convert("operation::some-operation[snippets='curl-request,http-request']", this.options); assertThat(result).isEqualTo(getExpectedContentFromFile("multiple-snippets")); } @Test - public void useMacroWithoutSnippetAttributeAddsAllSnippets() throws Exception { + void useMacroWithoutSnippetAttributeAddsAllSnippets() throws Exception { String result = this.asciidoctor.convert("operation::some-operation[]", this.options); assertThat(result).isEqualTo(getExpectedContentFromFile("all-snippets")); } @Test - public void useMacroWithEmptySnippetAttributeAddsAllSnippets() throws Exception { + void useMacroWithEmptySnippetAttributeAddsAllSnippets() throws Exception { String result = this.asciidoctor.convert("operation::some-operation[snippets=]", this.options); assertThat(result).isEqualTo(getExpectedContentFromFile("all-snippets")); } @Test - public void includingMissingSnippetAddsWarning() throws Exception { + void includingMissingSnippetAddsWarning() throws Exception { String result = this.asciidoctor.convert("operation::some-operation[snippets='missing-snippet']", this.options); assertThat(result).startsWith(getExpectedContentFromFile("missing-snippet")); assertThat(CapturingLogHandler.getLogRecords()).hasSize(1); @@ -177,13 +177,13 @@ public void includingMissingSnippetAddsWarning() throws Exception { } @Test - public void defaultTitleIsProvidedForCustomSnippet() throws Exception { + void defaultTitleIsProvidedForCustomSnippet() throws Exception { String result = this.asciidoctor.convert("operation::some-operation[snippets='custom-snippet']", this.options); assertThat(result).isEqualTo(getExpectedContentFromFile("custom-snippet-default-title")); } @Test - public void missingOperationIsHandledGracefully() throws Exception { + void missingOperationIsHandledGracefully() throws Exception { String result = this.asciidoctor.convert("operation::missing-operation[]", this.options); assertThat(result).startsWith(getExpectedContentFromFile("missing-operation")); assertThat(CapturingLogHandler.getLogRecords()).hasSize(1); @@ -194,14 +194,14 @@ public void missingOperationIsHandledGracefully() throws Exception { } @Test - public void titleOfBuiltInSnippetCanBeCustomizedUsingDocumentAttribute() throws URISyntaxException, IOException { + void titleOfBuiltInSnippetCanBeCustomizedUsingDocumentAttribute() throws URISyntaxException, IOException { String result = this.asciidoctor.convert(":operation-curl-request-title: Example request\n" + "operation::some-operation[snippets='curl-request']", this.options); assertThat(result).isEqualTo(getExpectedContentFromFile("built-in-snippet-custom-title")); } @Test - public void titleOfCustomSnippetCanBeCustomizedUsingDocumentAttribute() throws Exception { + void titleOfCustomSnippetCanBeCustomizedUsingDocumentAttribute() throws Exception { String result = this.asciidoctor.convert(":operation-custom-snippet-title: Customized title\n" + "operation::some-operation[snippets='custom-snippet']", this.options); assertThat(result).isEqualTo(getExpectedContentFromFile("custom-snippet-custom-title")); @@ -234,11 +234,12 @@ private File configurePdfOutput() { } private List extractStrings(File pdfFile) throws IOException { - PDDocument pdf = PDDocument.load(pdfFile); - assertThat(pdf.getNumberOfPages()).isEqualTo(1); - StringExtractor stringExtractor = new StringExtractor(); - stringExtractor.processPage(pdf.getPage(0)); - return stringExtractor.getStrings(); + try (PDDocument pdf = Loader.loadPDF(pdfFile)) { + assertThat(pdf.getNumberOfPages()).isEqualTo(1); + StringExtractor stringExtractor = new StringExtractor(); + stringExtractor.processPage(pdf.getPage(0)); + return stringExtractor.getStrings(); + } } private static final class StringExtractor extends PDFStreamEngine { diff --git a/spring-restdocs-asciidoctor/src/test/java/org/springframework/restdocs/asciidoctor/DefaultAttributesPreprocessorTests.java b/spring-restdocs-asciidoctor/src/test/java/org/springframework/restdocs/asciidoctor/DefaultAttributesPreprocessorTests.java new file mode 100644 index 00000000..7690f2bd --- /dev/null +++ b/spring-restdocs-asciidoctor/src/test/java/org/springframework/restdocs/asciidoctor/DefaultAttributesPreprocessorTests.java @@ -0,0 +1,67 @@ +/* + * Copyright 2014-present 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.asciidoctor; + +import java.io.File; + +import org.asciidoctor.Asciidoctor; +import org.asciidoctor.Attributes; +import org.asciidoctor.Options; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link DefaultAttributesPreprocessor}. + * + * @author Andy Wilkinson + */ +class DefaultAttributesPreprocessorTests { + + @Test + void snippetsAttributeIsSet() { + String converted = createAsciidoctor().convert("{snippets}", createOptions("projectdir=../../..")); + assertThat(converted).contains("build" + File.separatorChar + "generated-snippets"); + } + + @Test + void snippetsAttributeFromConvertArgumentIsNotOverridden() { + String converted = createAsciidoctor().convert("{snippets}", + createOptions("snippets=custom projectdir=../../..")); + assertThat(converted).contains("custom"); + } + + @Test + void snippetsAttributeFromDocumentPreambleIsNotOverridden() { + String converted = createAsciidoctor().convert(":snippets: custom\n{snippets}", + createOptions("projectdir=../../..")); + assertThat(converted).contains("custom"); + } + + private Options createOptions(String attributes) { + Options options = Options.builder().build(); + options.setAttributes(Attributes.builder().arguments(attributes).build()); + return options; + } + + private Asciidoctor createAsciidoctor() { + Asciidoctor asciidoctor = Asciidoctor.Factory.create(); + asciidoctor.javaExtensionRegistry().preprocessor(new DefaultAttributesPreprocessor()); + return asciidoctor; + } + +} diff --git a/spring-restdocs-asciidoctor/src/test/java/org/springframework/restdocs/asciidoctor/GradleOperationBlockMacroTests.java b/spring-restdocs-asciidoctor/src/test/java/org/springframework/restdocs/asciidoctor/GradleOperationBlockMacroTests.java index b7997ffc..812ef9e5 100644 --- a/spring-restdocs-asciidoctor/src/test/java/org/springframework/restdocs/asciidoctor/GradleOperationBlockMacroTests.java +++ b/spring-restdocs-asciidoctor/src/test/java/org/springframework/restdocs/asciidoctor/GradleOperationBlockMacroTests.java @@ -19,47 +19,42 @@ import java.io.File; import org.asciidoctor.Attributes; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; -import org.junit.runners.Parameterized.Parameters; +import org.junit.jupiter.params.ParameterizedClass; +import org.junit.jupiter.params.provider.ValueSource; /** * Tests for Ruby operation block macro when used in a Gradle build. * * @author Andy Wilkinson */ -@RunWith(Parameterized.class) -public class GradleOperationBlockMacroTests extends AbstractOperationBlockMacroTests { +@ParameterizedClass +@ValueSource(strings = { "projectdir", "gradle-projectdir" }) +class GradleOperationBlockMacroTests extends AbstractOperationBlockMacroTests { private final String attributeName; - public GradleOperationBlockMacroTests(String attributeName) { + GradleOperationBlockMacroTests(String attributeName) { this.attributeName = attributeName; } - @Parameters(name = "{0}") - public static Object[] parameters() { - return new Object[] { "projectdir", "gradle-projectdir" }; - } - @Override protected Attributes getAttributes() { Attributes attributes = Attributes.builder() - .attribute(this.attributeName, new File(this.temp.getRoot(), "gradle-project").getAbsolutePath()) + .attribute(this.attributeName, new File(this.temp, "gradle-project").getAbsolutePath()) .build(); return attributes; } @Override protected File getBuildOutputLocation() { - File outputLocation = new File(this.temp.getRoot(), "gradle-project/build"); + File outputLocation = new File(this.temp, "gradle-project/build"); outputLocation.mkdirs(); return outputLocation; } @Override protected File getSourceLocation() { - File sourceLocation = new File(this.temp.getRoot(), "gradle-project/src/docs/asciidoc"); + File sourceLocation = new File(this.temp, "gradle-project/src/docs/asciidoc"); if (!sourceLocation.exists()) { sourceLocation.mkdirs(); } diff --git a/spring-restdocs-asciidoctor/src/test/java/org/springframework/restdocs/asciidoctor/MavenOperationBlockMacroTests.java b/spring-restdocs-asciidoctor/src/test/java/org/springframework/restdocs/asciidoctor/MavenOperationBlockMacroTests.java index 17f2db88..43b92515 100644 --- a/spring-restdocs-asciidoctor/src/test/java/org/springframework/restdocs/asciidoctor/MavenOperationBlockMacroTests.java +++ b/spring-restdocs-asciidoctor/src/test/java/org/springframework/restdocs/asciidoctor/MavenOperationBlockMacroTests.java @@ -20,23 +20,23 @@ import java.io.IOException; import org.asciidoctor.Attributes; -import org.junit.After; -import org.junit.Before; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; /** * Tests for Ruby operation block macro when used in a Maven build. * * @author Andy Wilkinson */ -public class MavenOperationBlockMacroTests extends AbstractOperationBlockMacroTests { +class MavenOperationBlockMacroTests extends AbstractOperationBlockMacroTests { - @Before - public void setMavenHome() { + @BeforeEach + void setMavenHome() { System.setProperty("maven.home", "maven-home"); } - @After - public void clearMavenHome() { + @AfterEach + void clearMavenHome() { System.clearProperty("maven.home"); } @@ -55,14 +55,14 @@ protected Attributes getAttributes() { @Override protected File getBuildOutputLocation() { - File outputLocation = new File(this.temp.getRoot(), "maven-project/target"); + File outputLocation = new File(this.temp, "maven-project/target"); outputLocation.mkdirs(); return outputLocation; } @Override protected File getSourceLocation() { - File sourceLocation = new File(this.temp.getRoot(), "maven-project/src/main/asciidoc"); + File sourceLocation = new File(this.temp, "maven-project/src/main/asciidoc"); if (!sourceLocation.exists()) { sourceLocation.mkdirs(); } diff --git a/spring-restdocs-asciidoctor/src/test/java/org/springframework/restdocs/asciidoctor/SnippetsDirectoryResolverTests.java b/spring-restdocs-asciidoctor/src/test/java/org/springframework/restdocs/asciidoctor/SnippetsDirectoryResolverTests.java index fb71059c..e676cd0f 100644 --- a/spring-restdocs-asciidoctor/src/test/java/org/springframework/restdocs/asciidoctor/SnippetsDirectoryResolverTests.java +++ b/spring-restdocs-asciidoctor/src/test/java/org/springframework/restdocs/asciidoctor/SnippetsDirectoryResolverTests.java @@ -21,9 +21,8 @@ import java.util.HashMap; import java.util.Map; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.TemporaryFolder; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatIllegalStateException; @@ -35,23 +34,23 @@ */ public class SnippetsDirectoryResolverTests { - @Rule - public TemporaryFolder temporaryFolder = new TemporaryFolder(); + @TempDir + File temp; @Test public void mavenProjectsUseTargetGeneratedSnippets() throws IOException { - this.temporaryFolder.newFile("pom.xml"); + new File(this.temp, "pom.xml").createNewFile(); Map attributes = new HashMap<>(); - attributes.put("docdir", new File(this.temporaryFolder.getRoot(), "src/main/asciidoc").getAbsolutePath()); + attributes.put("docdir", new File(this.temp, "src/main/asciidoc").getAbsolutePath()); File snippetsDirectory = getMavenSnippetsDirectory(attributes); assertThat(snippetsDirectory).isAbsolute(); - assertThat(snippetsDirectory).isEqualTo(new File(this.temporaryFolder.getRoot(), "target/generated-snippets")); + assertThat(snippetsDirectory).isEqualTo(new File(this.temp, "target/generated-snippets")); } @Test public void illegalStateExceptionWhenMavenPomCannotBeFound() { Map attributes = new HashMap<>(); - String docdir = new File(this.temporaryFolder.getRoot(), "src/main/asciidoc").getAbsolutePath(); + String docdir = new File(this.temp, "src/main/asciidoc").getAbsolutePath(); attributes.put("docdir", docdir); assertThatIllegalStateException().isThrownBy(() -> getMavenSnippetsDirectory(attributes)) .withMessage("pom.xml not found in '" + docdir + "' or above"); diff --git a/spring-restdocs-bom/build.gradle b/spring-restdocs-bom/build.gradle index bd0b4ce5..05f73087 100644 --- a/spring-restdocs-bom/build.gradle +++ b/spring-restdocs-bom/build.gradle @@ -1,4 +1,5 @@ plugins { + id 'org.springframework.restdocs.conventions' id "java-platform" id "maven-publish" } diff --git a/spring-restdocs-core/build.gradle b/spring-restdocs-core/build.gradle index 61bc9d25..25ad156d 100644 --- a/spring-restdocs-core/build.gradle +++ b/spring-restdocs-core/build.gradle @@ -1,91 +1,74 @@ +import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar + plugins { - id "io.spring.compatibility-test" version "0.0.4" + id 'org.springframework.restdocs.conventions' id "java-library" id "java-test-fixtures" id "maven-publish" - id "optional-dependencies" + id "org.springframework.restdocs.optional-dependencies" + id "com.gradleup.shadow" version "8.3.8" apply false } description = "Spring REST Docs Core" configurations { - jarjar jmustache testArtifacts.extendsFrom testRuntime } -task jmustacheRepackJar(type: Jar) { repackJar -> - repackJar.archiveBaseName = "restdocs-jmustache-repack" - repackJar.archiveVersion = jmustacheVersion - - doLast() { - ant { - taskdef name: "jarjar", classname: "com.tonicsystems.jarjar.JarJarTask", - classpath: configurations.jarjar.asPath - jarjar(destfile: repackJar.archiveFile.get()) { - configurations.jmustache.each { originalJar -> - zipfileset(src: originalJar, includes: "**/*.class") - } - rule(pattern: "com.samskivert.**", result: "org.springframework.restdocs.@1") - } - } - } +def jmustacheShadowJar = tasks.register("jmustacheShadowJar", ShadowJar) { + archiveBaseName = "restdocs-jmustache" + archiveVersion = jmustacheVersion + configurations = [ project.configurations.jmustache ] + include("*.jar") + include("com/samskivert/**/*.class") + relocate("com.samskivert", "org.springframework.restdocs") } dependencies { - implementation("com.fasterxml.jackson.core:jackson-databind") - implementation("org.springframework:spring-web") - implementation(files(jmustacheRepackJar)) - - internal(platform(project(":spring-restdocs-platform"))) + compileOnly("org.apiguardian:apiguardian-api") - jarjar("com.googlecode.jarjar:jarjar:1.3") + implementation("tools.jackson.core:jackson-databind") + implementation("org.springframework:spring-web") + implementation(files(jmustacheShadowJar)) jmustache(platform(project(":spring-restdocs-platform"))) jmustache("com.samskivert:jmustache@jar") optional(platform(project(":spring-restdocs-platform"))) optional("jakarta.validation:jakarta.validation-api") - optional("junit:junit") optional("org.hibernate.validator:hibernate-validator") optional("org.junit.jupiter:junit-jupiter-api") testFixturesApi(platform(project(":spring-restdocs-platform"))) - testFixturesApi("junit:junit") testFixturesApi("org.assertj:assertj-core") - testFixturesApi("org.hamcrest:hamcrest-core") - testFixturesImplementation(files(jmustacheRepackJar)) + testFixturesApi("org.junit.jupiter:junit-jupiter") + testFixturesApi("org.mockito:mockito-core") + + testFixturesCompileOnly("org.apiguardian:apiguardian-api") + + testFixturesImplementation(files(jmustacheShadowJar)) testFixturesImplementation("org.hamcrest:hamcrest-library") + testFixturesImplementation("org.mockito:mockito-core") testFixturesImplementation("org.springframework:spring-core") testFixturesImplementation("org.springframework:spring-web") - testImplementation("junit:junit") + testFixturesRuntimeOnly("org.junit.platform:junit-platform-launcher") + + testCompileOnly("org.apiguardian:apiguardian-api") + testImplementation("org.assertj:assertj-core") testImplementation("org.javamoney:moneta") testImplementation("org.mockito:mockito-core") testImplementation("org.springframework:spring-test") testRuntimeOnly("org.apache.tomcat.embed:tomcat-embed-el") + testRuntimeOnly("org.junit.platform:junit-platform-engine") } -jar { - dependsOn jmustacheRepackJar - from(zipTree(jmustacheRepackJar.archiveFile.get())) { +tasks.named("jar") { + dependsOn jmustacheShadowJar + from(zipTree(jmustacheShadowJar.map { it.archiveFile } )) { include "org/springframework/restdocs/**" } } - -components.java.withVariantsFromConfiguration(configurations.testFixturesApiElements) { - skip() -} - -components.java.withVariantsFromConfiguration(configurations.testFixturesRuntimeElements) { - skip() -} - -compatibilityTest { - dependency("Spring Framework") { springFramework -> - springFramework.groupId = "org.springframework" - springFramework.versions = ["6.0.+", "6.1.+"] - } -} diff --git a/spring-restdocs-core/src/main/java/org/springframework/restdocs/JUnitRestDocumentation.java b/spring-restdocs-core/src/main/java/org/springframework/restdocs/JUnitRestDocumentation.java deleted file mode 100644 index a89ddd12..00000000 --- a/spring-restdocs-core/src/main/java/org/springframework/restdocs/JUnitRestDocumentation.java +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright 2014-present 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; - -import org.junit.rules.TestRule; -import org.junit.runner.Description; -import org.junit.runners.model.Statement; - -/** - * A JUnit {@link TestRule} used to automatically manage the - * {@link RestDocumentationContext}. - * - * @author Andy Wilkinson - * @since 1.1.0 - */ -public class JUnitRestDocumentation implements RestDocumentationContextProvider, TestRule { - - private final ManualRestDocumentation delegate; - - /** - * Creates a new {@code JUnitRestDocumentation} instance that will generate snippets - * to <gradle/maven build path>/generated-snippet. - */ - public JUnitRestDocumentation() { - this.delegate = new ManualRestDocumentation(); - } - - /** - * Creates a new {@code JUnitRestDocumentation} instance that will generate snippets - * to the given {@code outputDirectory}. - * @param outputDirectory the output directory - */ - public JUnitRestDocumentation(String outputDirectory) { - this.delegate = new ManualRestDocumentation(outputDirectory); - } - - @Override - public Statement apply(final Statement base, final Description description) { - return new Statement() { - - @Override - public void evaluate() throws Throwable { - Class testClass = description.getTestClass(); - String methodName = description.getMethodName(); - JUnitRestDocumentation.this.delegate.beforeTest(testClass, methodName); - try { - base.evaluate(); - } - finally { - JUnitRestDocumentation.this.delegate.afterTest(); - } - } - - }; - - } - - @Override - public RestDocumentationContext beforeOperation() { - return this.delegate.beforeOperation(); - } - -} diff --git a/spring-restdocs-core/src/main/java/org/springframework/restdocs/ManualRestDocumentation.java b/spring-restdocs-core/src/main/java/org/springframework/restdocs/ManualRestDocumentation.java index aa0bf8f1..03205062 100644 --- a/spring-restdocs-core/src/main/java/org/springframework/restdocs/ManualRestDocumentation.java +++ b/spring-restdocs-core/src/main/java/org/springframework/restdocs/ManualRestDocumentation.java @@ -18,13 +18,18 @@ import java.io.File; +import org.jspecify.annotations.Nullable; +import org.junit.jupiter.api.extension.Extension; + +import org.springframework.util.Assert; + /** * {@code ManualRestDocumentation} is used to manually manage the * {@link RestDocumentationContext}. Primarly intended for use with TestNG, but suitable * for use in any environment where manual management of the context is required. *

- * Users of JUnit should use {@link JUnitRestDocumentation} and take advantage of its - * Rule-based support for automatic management of the context. + * Users of JUnit should use {@link RestDocumentationExtension} and take advantage of its + * {@link Extension}-based support for automatic management of the context. * * @author Andy Wilkinson * @since 1.1.0 @@ -33,7 +38,7 @@ public final class ManualRestDocumentation implements RestDocumentationContextPr private final File outputDirectory; - private StandardRestDocumentationContext context; + private @Nullable StandardRestDocumentationContext context; /** * Creates a new {@code ManualRestDocumentation} instance that will generate snippets @@ -66,9 +71,7 @@ private ManualRestDocumentation(File outputDirectory) { * @throws IllegalStateException if a context has already be created */ public void beforeTest(Class testClass, String testMethodName) { - if (this.context != null) { - throw new IllegalStateException("Context already exists. Did you forget to call afterTest()?"); - } + Assert.isNull(this.context, () -> "Context already exists. Did you forget to call afterTest()?"); this.context = new StandardRestDocumentationContext(testClass, testMethodName, this.outputDirectory); } @@ -82,6 +85,7 @@ public void afterTest() { @Override public RestDocumentationContext beforeOperation() { + Assert.notNull(this.context, () -> "Context is null. Did you forget to call beforeTest(Class, String)?"); this.context.getAndIncrementStepCount(); return this.context; } diff --git a/spring-restdocs-core/src/main/java/org/springframework/restdocs/RestDocumentationExtension.java b/spring-restdocs-core/src/main/java/org/springframework/restdocs/RestDocumentationExtension.java index d9f3cc84..398ad8ff 100644 --- a/spring-restdocs-core/src/main/java/org/springframework/restdocs/RestDocumentationExtension.java +++ b/spring-restdocs-core/src/main/java/org/springframework/restdocs/RestDocumentationExtension.java @@ -16,6 +16,7 @@ package org.springframework.restdocs; +import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.extension.AfterEachCallback; import org.junit.jupiter.api.extension.BeforeEachCallback; import org.junit.jupiter.api.extension.Extension; @@ -32,7 +33,7 @@ */ public class RestDocumentationExtension implements BeforeEachCallback, AfterEachCallback, ParameterResolver { - private final String outputDirectory; + private final @Nullable String outputDirectory; /** * Creates a new {@code RestDocumentationExtension} that will use the default output @@ -48,7 +49,7 @@ public RestDocumentationExtension() { * @param outputDirectory snippet output directory * @since 2.0.4 */ - public RestDocumentationExtension(String outputDirectory) { + public RestDocumentationExtension(@Nullable String outputDirectory) { this.outputDirectory = outputDirectory; } diff --git a/spring-restdocs-core/src/main/java/org/springframework/restdocs/cli/CliDocumentation.java b/spring-restdocs-core/src/main/java/org/springframework/restdocs/cli/CliDocumentation.java index 469c1796..9fea2dea 100644 --- a/spring-restdocs-core/src/main/java/org/springframework/restdocs/cli/CliDocumentation.java +++ b/spring-restdocs-core/src/main/java/org/springframework/restdocs/cli/CliDocumentation.java @@ -18,6 +18,8 @@ import java.util.Map; +import org.jspecify.annotations.Nullable; + import org.springframework.restdocs.snippet.Snippet; /** @@ -80,7 +82,7 @@ public static Snippet curlRequest(CommandFormatter commandFormatter) { * @return the snippet that will document the curl request * @since 1.2.0 */ - public static Snippet curlRequest(Map attributes, CommandFormatter commandFormatter) { + public static Snippet curlRequest(@Nullable Map attributes, CommandFormatter commandFormatter) { return new CurlRequestSnippet(attributes, commandFormatter); } @@ -126,7 +128,7 @@ public static Snippet httpieRequest(CommandFormatter commandFormatter) { * @return the snippet that will document the HTTPie request * @since 1.2.0 */ - public static Snippet httpieRequest(Map attributes, CommandFormatter commandFormatter) { + public static Snippet httpieRequest(@Nullable Map attributes, CommandFormatter commandFormatter) { return new HttpieRequestSnippet(attributes, commandFormatter); } diff --git a/spring-restdocs-core/src/main/java/org/springframework/restdocs/cli/CliOperationRequest.java b/spring-restdocs-core/src/main/java/org/springframework/restdocs/cli/CliOperationRequest.java index ebfb2dc6..4bf6ce54 100644 --- a/spring-restdocs-core/src/main/java/org/springframework/restdocs/cli/CliOperationRequest.java +++ b/spring-restdocs-core/src/main/java/org/springframework/restdocs/cli/CliOperationRequest.java @@ -26,8 +26,11 @@ import java.util.Map.Entry; import java.util.Set; +import org.jspecify.annotations.Nullable; + import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; +import org.springframework.lang.Contract; import org.springframework.restdocs.operation.OperationRequest; import org.springframework.restdocs.operation.OperationRequestPart; import org.springframework.restdocs.operation.RequestCookie; @@ -55,7 +58,7 @@ boolean isPutOrPost() { return HttpMethod.PUT.equals(this.delegate.getMethod()) || HttpMethod.POST.equals(this.delegate.getMethod()); } - String getBasicAuthCredentials() { + @Nullable String getBasicAuthCredentials() { List headerValue = this.delegate.getHeaders().get(HttpHeaders.AUTHORIZATION); if (BasicAuthHeaderFilter.isBasicAuthHeader(headerValue)) { return BasicAuthHeaderFilter.decodeBasicAuthHeader(headerValue); @@ -76,7 +79,7 @@ public String getContentAsString() { @Override public HttpHeaders getHeaders() { HttpHeaders filteredHeaders = new HttpHeaders(); - for (Entry> header : this.delegate.getHeaders().entrySet()) { + for (Entry> header : this.delegate.getHeaders().headerSet()) { if (allowedHeader(header)) { filteredHeaders.put(header.getKey(), header.getValue()); } @@ -132,7 +135,8 @@ public boolean allow(String name, List value) { return !(HttpHeaders.AUTHORIZATION.equals(name) && isBasicAuthHeader(value)); } - static boolean isBasicAuthHeader(List value) { + @Contract("null -> false") + static boolean isBasicAuthHeader(@Nullable List value) { return value != null && (!value.isEmpty()) && value.get(0).startsWith("Basic "); } diff --git a/spring-restdocs-core/src/main/java/org/springframework/restdocs/cli/CurlRequestSnippet.java b/spring-restdocs-core/src/main/java/org/springframework/restdocs/cli/CurlRequestSnippet.java index 0a63e0c4..5c6130cf 100644 --- a/spring-restdocs-core/src/main/java/org/springframework/restdocs/cli/CurlRequestSnippet.java +++ b/spring-restdocs-core/src/main/java/org/springframework/restdocs/cli/CurlRequestSnippet.java @@ -22,6 +22,8 @@ import java.util.Map; import java.util.Map.Entry; +import org.jspecify.annotations.Nullable; + import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; import org.springframework.restdocs.operation.Operation; @@ -61,11 +63,11 @@ protected CurlRequestSnippet(CommandFormatter commandFormatter) { /** * Creates a new {@code CurlRequestSnippet} with the given additional * {@code attributes} that will be included in the model during template rendering. - * The given {@code commandFormaatter} will be used to format the curl command. + * The given {@code commandFormatter} will be used to format the curl command. * @param attributes the additional attributes * @param commandFormatter the formatter for generating the snippet */ - protected CurlRequestSnippet(Map attributes, CommandFormatter commandFormatter) { + protected CurlRequestSnippet(@Nullable Map attributes, CommandFormatter commandFormatter) { super("curl-request", attributes); Assert.notNull(commandFormatter, "Command formatter must not be null"); this.commandFormatter = commandFormatter; @@ -132,7 +134,7 @@ private void writeHttpMethod(OperationRequest request, StringBuilder builder) { } private void writeHeaders(CliOperationRequest request, List lines) { - for (Entry> entry : request.getHeaders().entrySet()) { + for (Entry> entry : request.getHeaders().headerSet()) { for (String header : entry.getValue()) { if (StringUtils.hasText(request.getContentAsString()) && HttpHeaders.CONTENT_TYPE.equals(entry.getKey()) && MediaType.APPLICATION_FORM_URLENCODED.equals(request.getHeaders().getContentType())) { diff --git a/spring-restdocs-core/src/main/java/org/springframework/restdocs/cli/HttpieRequestSnippet.java b/spring-restdocs-core/src/main/java/org/springframework/restdocs/cli/HttpieRequestSnippet.java index 4c3d4902..8dc01f55 100644 --- a/spring-restdocs-core/src/main/java/org/springframework/restdocs/cli/HttpieRequestSnippet.java +++ b/spring-restdocs-core/src/main/java/org/springframework/restdocs/cli/HttpieRequestSnippet.java @@ -24,6 +24,8 @@ import java.util.Map; import java.util.Map.Entry; +import org.jspecify.annotations.Nullable; + import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; import org.springframework.restdocs.operation.FormParameters; @@ -66,7 +68,7 @@ protected HttpieRequestSnippet(CommandFormatter commandFormatter) { * @param attributes the additional attributes * @param commandFormatter the formatter for generating the snippet */ - protected HttpieRequestSnippet(Map attributes, CommandFormatter commandFormatter) { + protected HttpieRequestSnippet(@Nullable Map attributes, CommandFormatter commandFormatter) { super("httpie-request", attributes); Assert.notNull(commandFormatter, "Command formatter must not be null"); this.commandFormatter = commandFormatter; @@ -160,10 +162,12 @@ private void writeFormDataIfNecessary(OperationRequest request, List lin private void writeHeaders(OperationRequest request, List lines) { HttpHeaders headers = request.getHeaders(); - for (Entry> entry : headers.entrySet()) { - if (entry.getKey().equals(HttpHeaders.CONTENT_TYPE) - && headers.getContentType().isCompatibleWith(MediaType.APPLICATION_FORM_URLENCODED)) { - continue; + for (Entry> entry : headers.headerSet()) { + if (entry.getKey().equals(HttpHeaders.CONTENT_TYPE)) { + MediaType contentType = headers.getContentType(); + if (contentType != null && contentType.isCompatibleWith(MediaType.APPLICATION_FORM_URLENCODED)) { + continue; + } } for (String header : entry.getValue()) { // HTTPie adds Content-Type automatically with --form diff --git a/spring-restdocs-core/src/main/java/org/springframework/restdocs/cli/package-info.java b/spring-restdocs-core/src/main/java/org/springframework/restdocs/cli/package-info.java index b5838467..382f2d88 100644 --- a/spring-restdocs-core/src/main/java/org/springframework/restdocs/cli/package-info.java +++ b/spring-restdocs-core/src/main/java/org/springframework/restdocs/cli/package-info.java @@ -17,4 +17,7 @@ /** * Documenting CLI commands required to make a request to a RESTful API. */ +@NullMarked package org.springframework.restdocs.cli; + +import org.jspecify.annotations.NullMarked; diff --git a/spring-restdocs-core/src/main/java/org/springframework/restdocs/config/OperationPreprocessorsConfigurer.java b/spring-restdocs-core/src/main/java/org/springframework/restdocs/config/OperationPreprocessorsConfigurer.java index 453d469b..788f2be4 100644 --- a/spring-restdocs-core/src/main/java/org/springframework/restdocs/config/OperationPreprocessorsConfigurer.java +++ b/spring-restdocs-core/src/main/java/org/springframework/restdocs/config/OperationPreprocessorsConfigurer.java @@ -18,6 +18,8 @@ import java.util.Map; +import org.jspecify.annotations.Nullable; + import org.springframework.restdocs.RestDocumentationContext; import org.springframework.restdocs.generate.RestDocumentationGenerator; import org.springframework.restdocs.operation.preprocess.OperationPreprocessor; @@ -36,9 +38,9 @@ */ public abstract class OperationPreprocessorsConfigurer extends AbstractNestedConfigurer { - private OperationRequestPreprocessor defaultOperationRequestPreprocessor; + private @Nullable OperationRequestPreprocessor defaultOperationRequestPreprocessor; - private OperationResponsePreprocessor defaultOperationResponsePreprocessor; + private @Nullable OperationResponsePreprocessor defaultOperationResponsePreprocessor; /** * Creates a new {@code OperationPreprocessorConfigurer} with the given diff --git a/spring-restdocs-core/src/main/java/org/springframework/restdocs/config/RestDocumentationConfigurer.java b/spring-restdocs-core/src/main/java/org/springframework/restdocs/config/RestDocumentationConfigurer.java index 9018e89e..e6578423 100644 --- a/spring-restdocs-core/src/main/java/org/springframework/restdocs/config/RestDocumentationConfigurer.java +++ b/spring-restdocs-core/src/main/java/org/springframework/restdocs/config/RestDocumentationConfigurer.java @@ -22,6 +22,8 @@ import java.util.List; import java.util.Map; +import org.jspecify.annotations.Nullable; + import org.springframework.restdocs.RestDocumentationContext; import org.springframework.restdocs.mustache.Mustache; import org.springframework.restdocs.snippet.RestDocumentationContextPlaceholderResolverFactory; @@ -32,6 +34,7 @@ import org.springframework.restdocs.templates.TemplateFormats; import org.springframework.restdocs.templates.mustache.AsciidoctorTableCellContentLambda; import org.springframework.restdocs.templates.mustache.MustacheTemplateEngine; +import org.springframework.util.Assert; /** * Abstract base class for the configuration of Spring REST Docs. @@ -103,7 +106,7 @@ protected final void apply(Map configuration, RestDocumentationC private static final class TemplateEngineConfigurer extends AbstractConfigurer { - private TemplateEngine templateEngine; + private @Nullable TemplateEngine templateEngine; @Override public void apply(Map configuration, RestDocumentationContext context) { @@ -111,6 +114,7 @@ public void apply(Map configuration, RestDocumentationContext co if (engineToUse == null) { SnippetConfiguration snippetConfiguration = (SnippetConfiguration) configuration .get(SnippetConfiguration.class.getName()); + Assert.notNull(snippetConfiguration, () -> "Snippet configuration unavailable"); Map templateContext = new HashMap<>(); if (snippetConfiguration.getTemplateFormat().getId().equals(TemplateFormats.asciidoctor().getId())) { templateContext.put("tableCellContent", new AsciidoctorTableCellContentLambda()); @@ -131,7 +135,7 @@ private void setTemplateEngine(TemplateEngine templateEngine) { private static final class WriterResolverConfigurer extends AbstractConfigurer { - private WriterResolver writerResolver; + private @Nullable WriterResolver writerResolver; @Override public void apply(Map configuration, RestDocumentationContext context) { @@ -139,6 +143,7 @@ public void apply(Map configuration, RestDocumentationContext co if (resolverToUse == null) { SnippetConfiguration snippetConfiguration = (SnippetConfiguration) configuration .get(SnippetConfiguration.class.getName()); + Assert.notNull(snippetConfiguration, () -> "Snippet configuration unavailable"); resolverToUse = new StandardWriterResolver(new RestDocumentationContextPlaceholderResolverFactory(), snippetConfiguration.getEncoding(), snippetConfiguration.getTemplateFormat()); } diff --git a/spring-restdocs-core/src/main/java/org/springframework/restdocs/config/package-info.java b/spring-restdocs-core/src/main/java/org/springframework/restdocs/config/package-info.java index 9b085837..0553d345 100644 --- a/spring-restdocs-core/src/main/java/org/springframework/restdocs/config/package-info.java +++ b/spring-restdocs-core/src/main/java/org/springframework/restdocs/config/package-info.java @@ -17,4 +17,7 @@ /** * Classes for configuring Spring REST Docs. */ +@NullMarked package org.springframework.restdocs.config; + +import org.jspecify.annotations.NullMarked; diff --git a/spring-restdocs-core/src/main/java/org/springframework/restdocs/constraints/ResourceBundleConstraintDescriptionResolver.java b/spring-restdocs-core/src/main/java/org/springframework/restdocs/constraints/ResourceBundleConstraintDescriptionResolver.java index ae138c48..b42d17d2 100644 --- a/spring-restdocs-core/src/main/java/org/springframework/restdocs/constraints/ResourceBundleConstraintDescriptionResolver.java +++ b/spring-restdocs-core/src/main/java/org/springframework/restdocs/constraints/ResourceBundleConstraintDescriptionResolver.java @@ -52,7 +52,9 @@ import org.hibernate.validator.constraints.Mod11Check; import org.hibernate.validator.constraints.Range; import org.hibernate.validator.constraints.URL; +import org.jspecify.annotations.Nullable; +import org.springframework.util.Assert; import org.springframework.util.PropertyPlaceholderHelper; import org.springframework.util.PropertyPlaceholderHelper.PlaceholderResolver; import org.springframework.util.StringUtils; @@ -64,7 +66,7 @@ * {@code jakarta.validation.constraints.NotNull} is * {@code jakarta.validation.constraints.NotNull.description}. *

- * Default descriptions are provided for Bean Validation 2.0's constraints: + * Default descriptions are provided for all of Bean Validation 3.1's constraints: * *

    *
  • {@link AssertFalse} @@ -92,20 +94,18 @@ *
* *

- * Default descriptions are also provided for Hibernate Validator's constraints: + * Default descriptions are also provided for the following Hibernate Validator + * constraints: * *

    *
  • {@link CodePointLength} *
  • {@link CreditCardNumber} *
  • {@link Currency} *
  • {@link EAN} - *
  • {@link org.hibernate.validator.constraints.Email} *
  • {@link Length} *
  • {@link LuhnCheck} *
  • {@link Mod10Check} *
  • {@link Mod11Check} - *
  • {@link org.hibernate.validator.constraints.NotBlank} - *
  • {@link org.hibernate.validator.constraints.NotEmpty} *
  • {@link Range} *
  • {@link URL} *
@@ -116,9 +116,9 @@ public class ResourceBundleConstraintDescriptionResolver implements ConstraintDe private final PropertyPlaceholderHelper propertyPlaceholderHelper = new PropertyPlaceholderHelper("${", "}"); - private final ResourceBundle defaultDescriptions; + private final ResourceBundle defaultDescriptions = getDefaultDescriptions(); - private final ResourceBundle userDescriptions; + private final @Nullable ResourceBundle userDescriptions; /** * Creates a new {@code ResourceBundleConstraintDescriptionResolver} that will resolve @@ -127,7 +127,7 @@ public class ResourceBundleConstraintDescriptionResolver implements ConstraintDe * default locale loaded using the thread context class loader. */ public ResourceBundleConstraintDescriptionResolver() { - this(getBundle("ConstraintDescriptions")); + this.userDescriptions = getBundle("ConstraintDescriptions"); } /** @@ -136,11 +136,16 @@ public ResourceBundleConstraintDescriptionResolver() { * @param resourceBundle the resource bundle */ public ResourceBundleConstraintDescriptionResolver(ResourceBundle resourceBundle) { - this.defaultDescriptions = getBundle("DefaultConstraintDescriptions"); this.userDescriptions = resourceBundle; } - private static ResourceBundle getBundle(String name) { + private static ResourceBundle getDefaultDescriptions() { + ResourceBundle bundle = getBundle("DefaultConstraintDescriptions"); + Assert.notNull(bundle, () -> "Failed to load default constraint descriptions"); + return bundle; + } + + private static @Nullable ResourceBundle getBundle(String name) { try { return ResourceBundle.getBundle( ResourceBundleConstraintDescriptionResolver.class.getPackage().getName() + "." + name, @@ -179,7 +184,7 @@ private ConstraintPlaceholderResolver(Constraint constraint) { } @Override - public String resolvePlaceholder(String placeholderName) { + public @Nullable String resolvePlaceholder(String placeholderName) { Object replacement = this.constraint.getConfiguration().get(placeholderName); if (replacement == null) { return null; diff --git a/spring-restdocs-core/src/main/java/org/springframework/restdocs/constraints/package-info.java b/spring-restdocs-core/src/main/java/org/springframework/restdocs/constraints/package-info.java index c9de99af..fe7b8fb8 100644 --- a/spring-restdocs-core/src/main/java/org/springframework/restdocs/constraints/package-info.java +++ b/spring-restdocs-core/src/main/java/org/springframework/restdocs/constraints/package-info.java @@ -17,4 +17,7 @@ /** * Documenting a RESTful API's constraints. */ +@NullMarked package org.springframework.restdocs.constraints; + +import org.jspecify.annotations.NullMarked; diff --git a/spring-restdocs-core/src/main/java/org/springframework/restdocs/cookies/AbstractCookiesSnippet.java b/spring-restdocs-core/src/main/java/org/springframework/restdocs/cookies/AbstractCookiesSnippet.java index 68fbc910..0cd8aebc 100644 --- a/spring-restdocs-core/src/main/java/org/springframework/restdocs/cookies/AbstractCookiesSnippet.java +++ b/spring-restdocs-core/src/main/java/org/springframework/restdocs/cookies/AbstractCookiesSnippet.java @@ -26,6 +26,8 @@ import java.util.Map.Entry; import java.util.Set; +import org.jspecify.annotations.Nullable; + import org.springframework.restdocs.operation.Operation; import org.springframework.restdocs.snippet.TemplatedSnippet; import org.springframework.util.Assert; @@ -54,8 +56,8 @@ public abstract class AbstractCookiesSnippet extends TemplatedSnippet { * @param attributes the additional attributes * @param ignoreUndocumentedCookies whether undocumented cookies should be ignored */ - protected AbstractCookiesSnippet(String type, List descriptors, Map attributes, - boolean ignoreUndocumentedCookies) { + protected AbstractCookiesSnippet(String type, List descriptors, + @Nullable Map attributes, boolean ignoreUndocumentedCookies) { super(type + "-cookies", attributes); for (CookieDescriptor descriptor : descriptors) { Assert.notNull(descriptor.getName(), "Cookie descriptors must have a name"); diff --git a/spring-restdocs-core/src/main/java/org/springframework/restdocs/cookies/RequestCookiesSnippet.java b/spring-restdocs-core/src/main/java/org/springframework/restdocs/cookies/RequestCookiesSnippet.java index 7e77243b..35567e33 100644 --- a/spring-restdocs-core/src/main/java/org/springframework/restdocs/cookies/RequestCookiesSnippet.java +++ b/spring-restdocs-core/src/main/java/org/springframework/restdocs/cookies/RequestCookiesSnippet.java @@ -23,6 +23,8 @@ import java.util.Map; import java.util.Set; +import org.jspecify.annotations.Nullable; + import org.springframework.restdocs.operation.Operation; import org.springframework.restdocs.operation.RequestCookie; import org.springframework.restdocs.snippet.Snippet; @@ -68,7 +70,7 @@ protected RequestCookiesSnippet(List descriptors, boolean igno * @param descriptors the descriptors * @param attributes the additional attributes */ - protected RequestCookiesSnippet(List descriptors, Map attributes) { + protected RequestCookiesSnippet(List descriptors, @Nullable Map attributes) { this(descriptors, attributes, false); } @@ -80,7 +82,7 @@ protected RequestCookiesSnippet(List descriptors, Map descriptors, Map attributes, + protected RequestCookiesSnippet(List descriptors, @Nullable Map attributes, boolean ignoreUndocumentedCookies) { super("request", descriptors, attributes, ignoreUndocumentedCookies); } diff --git a/spring-restdocs-core/src/main/java/org/springframework/restdocs/cookies/ResponseCookiesSnippet.java b/spring-restdocs-core/src/main/java/org/springframework/restdocs/cookies/ResponseCookiesSnippet.java index 2634042e..3fc90539 100644 --- a/spring-restdocs-core/src/main/java/org/springframework/restdocs/cookies/ResponseCookiesSnippet.java +++ b/spring-restdocs-core/src/main/java/org/springframework/restdocs/cookies/ResponseCookiesSnippet.java @@ -23,6 +23,8 @@ import java.util.Set; import java.util.stream.Collectors; +import org.jspecify.annotations.Nullable; + import org.springframework.restdocs.operation.Operation; import org.springframework.restdocs.operation.ResponseCookie; import org.springframework.restdocs.snippet.Snippet; @@ -68,7 +70,7 @@ protected ResponseCookiesSnippet(List descriptors, boolean ign * @param descriptors the descriptors * @param attributes the additional attributes */ - protected ResponseCookiesSnippet(List descriptors, Map attributes) { + protected ResponseCookiesSnippet(List descriptors, @Nullable Map attributes) { this(descriptors, attributes, false); } @@ -80,7 +82,7 @@ protected ResponseCookiesSnippet(List descriptors, Map descriptors, Map attributes, + protected ResponseCookiesSnippet(List descriptors, @Nullable Map attributes, boolean ignoreUndocumentedCookies) { super("response", descriptors, attributes, ignoreUndocumentedCookies); } diff --git a/spring-restdocs-core/src/main/java/org/springframework/restdocs/cookies/package-info.java b/spring-restdocs-core/src/main/java/org/springframework/restdocs/cookies/package-info.java index a84567f0..94bbdb7e 100644 --- a/spring-restdocs-core/src/main/java/org/springframework/restdocs/cookies/package-info.java +++ b/spring-restdocs-core/src/main/java/org/springframework/restdocs/cookies/package-info.java @@ -17,4 +17,7 @@ /** * Documenting the cookies of a RESTful API's requests and responses. */ +@NullMarked package org.springframework.restdocs.cookies; + +import org.jspecify.annotations.NullMarked; diff --git a/spring-restdocs-core/src/main/java/org/springframework/restdocs/generate/package-info.java b/spring-restdocs-core/src/main/java/org/springframework/restdocs/generate/package-info.java index 7abf2f14..c5f2f23f 100644 --- a/spring-restdocs-core/src/main/java/org/springframework/restdocs/generate/package-info.java +++ b/spring-restdocs-core/src/main/java/org/springframework/restdocs/generate/package-info.java @@ -17,4 +17,7 @@ /** * Classes that drive the generation of the documentation snippets. */ +@NullMarked package org.springframework.restdocs.generate; + +import org.jspecify.annotations.NullMarked; diff --git a/spring-restdocs-core/src/main/java/org/springframework/restdocs/headers/AbstractHeadersSnippet.java b/spring-restdocs-core/src/main/java/org/springframework/restdocs/headers/AbstractHeadersSnippet.java index 5f9a0c1b..b1c0b5a2 100644 --- a/spring-restdocs-core/src/main/java/org/springframework/restdocs/headers/AbstractHeadersSnippet.java +++ b/spring-restdocs-core/src/main/java/org/springframework/restdocs/headers/AbstractHeadersSnippet.java @@ -22,6 +22,8 @@ import java.util.Map; import java.util.Set; +import org.jspecify.annotations.Nullable; + import org.springframework.restdocs.operation.Operation; import org.springframework.restdocs.snippet.SnippetException; import org.springframework.restdocs.snippet.TemplatedSnippet; @@ -48,7 +50,8 @@ public abstract class AbstractHeadersSnippet extends TemplatedSnippet { * @param descriptors the header descriptors * @param attributes the additional attributes */ - protected AbstractHeadersSnippet(String type, List descriptors, Map attributes) { + protected AbstractHeadersSnippet(String type, List descriptors, + @Nullable Map attributes) { super(type + "-headers", attributes); for (HeaderDescriptor descriptor : descriptors) { Assert.notNull(descriptor.getName(), "The name of the header must not be null"); diff --git a/spring-restdocs-core/src/main/java/org/springframework/restdocs/headers/RequestHeadersSnippet.java b/spring-restdocs-core/src/main/java/org/springframework/restdocs/headers/RequestHeadersSnippet.java index bc192d13..f8ce2c17 100644 --- a/spring-restdocs-core/src/main/java/org/springframework/restdocs/headers/RequestHeadersSnippet.java +++ b/spring-restdocs-core/src/main/java/org/springframework/restdocs/headers/RequestHeadersSnippet.java @@ -22,6 +22,8 @@ import java.util.Map; import java.util.Set; +import org.jspecify.annotations.Nullable; + import org.springframework.restdocs.operation.Operation; import org.springframework.restdocs.snippet.Snippet; @@ -51,13 +53,13 @@ protected RequestHeadersSnippet(List descriptors) { * @param descriptors the descriptors * @param attributes the additional attributes */ - protected RequestHeadersSnippet(List descriptors, Map attributes) { + protected RequestHeadersSnippet(List descriptors, @Nullable Map attributes) { super("request", descriptors, attributes); } @Override protected Set extractActualHeaders(Operation operation) { - return operation.getRequest().getHeaders().keySet(); + return operation.getRequest().getHeaders().headerNames(); } /** diff --git a/spring-restdocs-core/src/main/java/org/springframework/restdocs/headers/ResponseHeadersSnippet.java b/spring-restdocs-core/src/main/java/org/springframework/restdocs/headers/ResponseHeadersSnippet.java index fc86ee45..d72c3caf 100644 --- a/spring-restdocs-core/src/main/java/org/springframework/restdocs/headers/ResponseHeadersSnippet.java +++ b/spring-restdocs-core/src/main/java/org/springframework/restdocs/headers/ResponseHeadersSnippet.java @@ -22,6 +22,8 @@ import java.util.Map; import java.util.Set; +import org.jspecify.annotations.Nullable; + import org.springframework.restdocs.operation.Operation; import org.springframework.restdocs.snippet.Snippet; @@ -51,13 +53,13 @@ protected ResponseHeadersSnippet(List descriptors) { * @param descriptors the descriptors * @param attributes the additional attributes */ - protected ResponseHeadersSnippet(List descriptors, Map attributes) { + protected ResponseHeadersSnippet(List descriptors, @Nullable Map attributes) { super("response", descriptors, attributes); } @Override protected Set extractActualHeaders(Operation operation) { - return operation.getResponse().getHeaders().keySet(); + return operation.getResponse().getHeaders().headerNames(); } /** diff --git a/spring-restdocs-core/src/main/java/org/springframework/restdocs/headers/package-info.java b/spring-restdocs-core/src/main/java/org/springframework/restdocs/headers/package-info.java index ebaa61a8..1f7dd449 100644 --- a/spring-restdocs-core/src/main/java/org/springframework/restdocs/headers/package-info.java +++ b/spring-restdocs-core/src/main/java/org/springframework/restdocs/headers/package-info.java @@ -17,4 +17,7 @@ /** * Documenting the headers of a RESTful API's requests and responses. */ +@NullMarked package org.springframework.restdocs.headers; + +import org.jspecify.annotations.NullMarked; diff --git a/spring-restdocs-core/src/main/java/org/springframework/restdocs/http/HttpRequestSnippet.java b/spring-restdocs-core/src/main/java/org/springframework/restdocs/http/HttpRequestSnippet.java index 8a62f648..4639a1a9 100644 --- a/spring-restdocs-core/src/main/java/org/springframework/restdocs/http/HttpRequestSnippet.java +++ b/spring-restdocs-core/src/main/java/org/springframework/restdocs/http/HttpRequestSnippet.java @@ -24,6 +24,8 @@ import java.util.Map; import java.util.Map.Entry; +import org.jspecify.annotations.Nullable; + import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.MediaType; @@ -58,7 +60,7 @@ protected HttpRequestSnippet() { * {@code attributes} that will be included in the model during template rendering. * @param attributes the additional attributes */ - protected HttpRequestSnippet(Map attributes) { + protected HttpRequestSnippet(@Nullable Map attributes) { super("http-request", attributes); } @@ -91,7 +93,7 @@ private boolean includeParametersInUri(OperationRequest request) { private List> getHeaders(OperationRequest request) { List> headers = new ArrayList<>(); - for (Entry> header : request.getHeaders().entrySet()) { + for (Entry> header : request.getHeaders().headerSet()) { for (String value : header.getValue()) { if (HttpHeaders.CONTENT_TYPE.equals(header.getKey()) && !request.getParts().isEmpty()) { headers.add(header(header.getKey(), String.format("%s; boundary=%s", value, MULTIPART_BOUNDARY))); @@ -156,7 +158,8 @@ private void writePart(OperationRequestPart part, PrintWriter writer) { part.getHeaders().getContentType(), writer); } - private void writePart(String name, String value, String filename, MediaType contentType, PrintWriter writer) { + private void writePart(String name, String value, @Nullable String filename, @Nullable MediaType contentType, + PrintWriter writer) { writer.printf("Content-Disposition: form-data; name=%s", name); if (StringUtils.hasText(filename)) { writer.printf("; filename=%s", filename); diff --git a/spring-restdocs-core/src/main/java/org/springframework/restdocs/http/HttpResponseSnippet.java b/spring-restdocs-core/src/main/java/org/springframework/restdocs/http/HttpResponseSnippet.java index 23f9dae9..89a0ae62 100644 --- a/spring-restdocs-core/src/main/java/org/springframework/restdocs/http/HttpResponseSnippet.java +++ b/spring-restdocs-core/src/main/java/org/springframework/restdocs/http/HttpResponseSnippet.java @@ -22,6 +22,8 @@ import java.util.Map; import java.util.Map.Entry; +import org.jspecify.annotations.Nullable; + import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatusCode; import org.springframework.restdocs.operation.Operation; @@ -50,7 +52,7 @@ protected HttpResponseSnippet() { * {@code attributes} that will be included in the model during template rendering. * @param attributes the additional attributes */ - protected HttpResponseSnippet(Map attributes) { + protected HttpResponseSnippet(@Nullable Map attributes) { super("http-response", attributes); } @@ -73,7 +75,7 @@ private String responseBody(OperationResponse response) { private List> headers(OperationResponse response) { List> headers = new ArrayList<>(); - for (Entry> header : response.getHeaders().entrySet()) { + for (Entry> header : response.getHeaders().headerSet()) { List values = header.getValue(); for (String value : values) { headers.add(header(header.getKey(), value)); diff --git a/spring-restdocs-core/src/main/java/org/springframework/restdocs/http/package-info.java b/spring-restdocs-core/src/main/java/org/springframework/restdocs/http/package-info.java index 48eca1f6..5f317561 100644 --- a/spring-restdocs-core/src/main/java/org/springframework/restdocs/http/package-info.java +++ b/spring-restdocs-core/src/main/java/org/springframework/restdocs/http/package-info.java @@ -18,4 +18,7 @@ * Documenting the HTTP request sent to a RESTful API and the HTTP response that is * returned. */ +@NullMarked package org.springframework.restdocs.http; + +import org.jspecify.annotations.NullMarked; diff --git a/spring-restdocs-core/src/main/java/org/springframework/restdocs/hypermedia/AbstractJsonLinkExtractor.java b/spring-restdocs-core/src/main/java/org/springframework/restdocs/hypermedia/AbstractJsonLinkExtractor.java index d20af837..b934106b 100644 --- a/spring-restdocs-core/src/main/java/org/springframework/restdocs/hypermedia/AbstractJsonLinkExtractor.java +++ b/spring-restdocs-core/src/main/java/org/springframework/restdocs/hypermedia/AbstractJsonLinkExtractor.java @@ -20,7 +20,7 @@ import java.util.List; import java.util.Map; -import com.fasterxml.jackson.databind.ObjectMapper; +import tools.jackson.databind.ObjectMapper; import org.springframework.restdocs.operation.OperationResponse; diff --git a/spring-restdocs-core/src/main/java/org/springframework/restdocs/hypermedia/AtomLinkExtractor.java b/spring-restdocs-core/src/main/java/org/springframework/restdocs/hypermedia/AtomLinkExtractor.java index f145058e..a0895b3a 100644 --- a/spring-restdocs-core/src/main/java/org/springframework/restdocs/hypermedia/AtomLinkExtractor.java +++ b/spring-restdocs-core/src/main/java/org/springframework/restdocs/hypermedia/AtomLinkExtractor.java @@ -20,6 +20,8 @@ import java.util.List; import java.util.Map; +import org.jspecify.annotations.Nullable; + import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; @@ -47,7 +49,7 @@ public Map> extractLinks(Map json) { return extractedLinks; } - private static Link maybeCreateLink(Map linkMap) { + private static @Nullable Link maybeCreateLink(Map linkMap) { Object hrefObject = linkMap.get("href"); Object relObject = linkMap.get("rel"); if (relObject instanceof String && hrefObject instanceof String) { @@ -58,7 +60,7 @@ private static Link maybeCreateLink(Map linkMap) { return null; } - private static void maybeStoreLink(Link link, MultiValueMap extractedLinks) { + private static void maybeStoreLink(@Nullable Link link, MultiValueMap extractedLinks) { if (link != null) { extractedLinks.add(link.getRel(), link); } diff --git a/spring-restdocs-core/src/main/java/org/springframework/restdocs/hypermedia/ContentTypeLinkExtractor.java b/spring-restdocs-core/src/main/java/org/springframework/restdocs/hypermedia/ContentTypeLinkExtractor.java index b97e7c61..ff5d0fed 100644 --- a/spring-restdocs-core/src/main/java/org/springframework/restdocs/hypermedia/ContentTypeLinkExtractor.java +++ b/spring-restdocs-core/src/main/java/org/springframework/restdocs/hypermedia/ContentTypeLinkExtractor.java @@ -22,6 +22,8 @@ import java.util.Map; import java.util.Map.Entry; +import org.jspecify.annotations.Nullable; + import org.springframework.http.MediaType; import org.springframework.restdocs.operation.OperationResponse; @@ -30,6 +32,7 @@ * content type. * * @author Andy Wilkinson + * @author Oliver Drotbohm */ class ContentTypeLinkExtractor implements LinkExtractor { @@ -37,7 +40,10 @@ class ContentTypeLinkExtractor implements LinkExtractor { ContentTypeLinkExtractor() { this.linkExtractors.put(MediaType.APPLICATION_JSON, new AtomLinkExtractor()); - this.linkExtractors.put(HalLinkExtractor.HAL_MEDIA_TYPE, new HalLinkExtractor()); + LinkExtractor halLinkExtractor = new HalLinkExtractor(); + this.linkExtractors.put(HalLinkExtractor.HAL_MEDIA_TYPE, halLinkExtractor); + this.linkExtractors.put(HalLinkExtractor.VND_HAL_MEDIA_TYPE, halLinkExtractor); + this.linkExtractors.put(HalLinkExtractor.HAL_FORMS_MEDIA_TYPE, halLinkExtractor); } ContentTypeLinkExtractor(Map linkExtractors) { @@ -55,7 +61,7 @@ public Map> extractLinks(OperationResponse response) throws I "No LinkExtractor has been provided and one is not available for the " + "content type " + contentType); } - private LinkExtractor getExtractorForContentType(MediaType contentType) { + private @Nullable LinkExtractor getExtractorForContentType(@Nullable MediaType contentType) { if (contentType != null) { for (Entry entry : this.linkExtractors.entrySet()) { if (contentType.isCompatibleWith(entry.getKey())) { diff --git a/spring-restdocs-core/src/main/java/org/springframework/restdocs/hypermedia/HalLinkExtractor.java b/spring-restdocs-core/src/main/java/org/springframework/restdocs/hypermedia/HalLinkExtractor.java index 3e2f6512..ab8cf4bd 100644 --- a/spring-restdocs-core/src/main/java/org/springframework/restdocs/hypermedia/HalLinkExtractor.java +++ b/spring-restdocs-core/src/main/java/org/springframework/restdocs/hypermedia/HalLinkExtractor.java @@ -23,6 +23,8 @@ import java.util.Map; import java.util.Map.Entry; +import org.jspecify.annotations.Nullable; + import org.springframework.http.MediaType; /** @@ -30,11 +32,16 @@ * format. * * @author Andy Wilkinson + * @author Oliver Drotbohm */ class HalLinkExtractor extends AbstractJsonLinkExtractor { static final MediaType HAL_MEDIA_TYPE = new MediaType("application", "hal+json"); + static final MediaType VND_HAL_MEDIA_TYPE = new MediaType("application", "vnd.hal+json"); + + static final MediaType HAL_FORMS_MEDIA_TYPE = new MediaType("application", "prs.hal-forms+json"); + @Override public Map> extractLinks(Map json) { Map> extractedLinks = new LinkedHashMap<>(); @@ -65,7 +72,7 @@ private static List convertToLinks(Object object, String rel) { return links; } - private static Link maybeCreateLink(String rel, Object possibleLinkObject) { + private static @Nullable Link maybeCreateLink(String rel, Object possibleLinkObject) { if (possibleLinkObject instanceof Map) { Map possibleLinkMap = (Map) possibleLinkObject; Object hrefObject = possibleLinkMap.get("href"); @@ -78,7 +85,7 @@ private static Link maybeCreateLink(String rel, Object possibleLinkObject) { return null; } - private static void maybeAddLink(Link possibleLink, List links) { + private static void maybeAddLink(@Nullable Link possibleLink, List links) { if (possibleLink != null) { links.add(possibleLink); } diff --git a/spring-restdocs-core/src/main/java/org/springframework/restdocs/hypermedia/Link.java b/spring-restdocs-core/src/main/java/org/springframework/restdocs/hypermedia/Link.java index 2442de22..b2d53d1c 100644 --- a/spring-restdocs-core/src/main/java/org/springframework/restdocs/hypermedia/Link.java +++ b/spring-restdocs-core/src/main/java/org/springframework/restdocs/hypermedia/Link.java @@ -16,6 +16,8 @@ package org.springframework.restdocs.hypermedia; +import org.jspecify.annotations.Nullable; + import org.springframework.core.style.ToStringCreator; /** @@ -29,7 +31,7 @@ public class Link { private final String href; - private final String title; + private final @Nullable String title; /** * Creates a new {@code Link} with the given {@code rel} and {@code href}. @@ -47,7 +49,7 @@ public Link(String rel, String href) { * @param href the link's href * @param title the link's title */ - public Link(String rel, String href, String title) { + public Link(String rel, String href, @Nullable String title) { this.rel = rel; this.href = href; this.title = title; @@ -73,7 +75,7 @@ public String getHref() { * Returns the link's {@code title}, or {@code null} if it does not have a title. * @return the link's {@code title} or {@code null} */ - public String getTitle() { + public @Nullable String getTitle() { return this.title; } diff --git a/spring-restdocs-core/src/main/java/org/springframework/restdocs/hypermedia/LinksSnippet.java b/spring-restdocs-core/src/main/java/org/springframework/restdocs/hypermedia/LinksSnippet.java index 9a23ec27..fe693a88 100644 --- a/spring-restdocs-core/src/main/java/org/springframework/restdocs/hypermedia/LinksSnippet.java +++ b/spring-restdocs-core/src/main/java/org/springframework/restdocs/hypermedia/LinksSnippet.java @@ -28,6 +28,8 @@ import java.util.Map.Entry; import java.util.Set; +import org.jspecify.annotations.Nullable; + import org.springframework.restdocs.operation.Operation; import org.springframework.restdocs.operation.OperationResponse; import org.springframework.restdocs.snippet.ModelCreationException; @@ -88,7 +90,7 @@ protected LinksSnippet(LinkExtractor linkExtractor, List descrip * @param attributes the additional attributes */ protected LinksSnippet(LinkExtractor linkExtractor, List descriptors, - Map attributes) { + @Nullable Map attributes) { this(linkExtractor, descriptors, attributes, false); } @@ -104,7 +106,7 @@ protected LinksSnippet(LinkExtractor linkExtractor, List descrip * @param ignoreUndocumentedLinks whether undocumented links should be ignored */ protected LinksSnippet(LinkExtractor linkExtractor, List descriptors, - Map attributes, boolean ignoreUndocumentedLinks) { + @Nullable Map attributes, boolean ignoreUndocumentedLinks) { super("links", attributes); this.linkExtractor = linkExtractor; for (LinkDescriptor descriptor : descriptors) { diff --git a/spring-restdocs-core/src/main/java/org/springframework/restdocs/hypermedia/package-info.java b/spring-restdocs-core/src/main/java/org/springframework/restdocs/hypermedia/package-info.java index b4b6be0a..66bd3093 100644 --- a/spring-restdocs-core/src/main/java/org/springframework/restdocs/hypermedia/package-info.java +++ b/spring-restdocs-core/src/main/java/org/springframework/restdocs/hypermedia/package-info.java @@ -17,4 +17,7 @@ /** * Documenting a RESTful API that uses hypermedia. */ +@NullMarked package org.springframework.restdocs.hypermedia; + +import org.jspecify.annotations.NullMarked; diff --git a/spring-restdocs-core/src/main/java/org/springframework/restdocs/operation/AbstractOperationMessage.java b/spring-restdocs-core/src/main/java/org/springframework/restdocs/operation/AbstractOperationMessage.java index 27751bb3..526a6372 100644 --- a/spring-restdocs-core/src/main/java/org/springframework/restdocs/operation/AbstractOperationMessage.java +++ b/spring-restdocs-core/src/main/java/org/springframework/restdocs/operation/AbstractOperationMessage.java @@ -20,6 +20,8 @@ import java.nio.charset.StandardCharsets; import java.util.Arrays; +import org.jspecify.annotations.Nullable; + import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; @@ -34,7 +36,7 @@ abstract class AbstractOperationMessage implements OperationMessage { private final HttpHeaders headers; - AbstractOperationMessage(byte[] content, HttpHeaders headers) { + AbstractOperationMessage(byte @Nullable [] content, HttpHeaders headers) { this.content = (content != null) ? content : new byte[0]; this.headers = headers; } @@ -61,7 +63,7 @@ public String getContentAsString() { return ""; } - private Charset extractCharsetFromContentTypeHeader() { + private @Nullable Charset extractCharsetFromContentTypeHeader() { if (this.headers == null) { return null; } diff --git a/spring-restdocs-core/src/main/java/org/springframework/restdocs/operation/HttpHeadersHelper.java b/spring-restdocs-core/src/main/java/org/springframework/restdocs/operation/HttpHeadersHelper.java index 544ee49c..3fb6a4b5 100644 --- a/spring-restdocs-core/src/main/java/org/springframework/restdocs/operation/HttpHeadersHelper.java +++ b/spring-restdocs-core/src/main/java/org/springframework/restdocs/operation/HttpHeadersHelper.java @@ -16,6 +16,8 @@ package org.springframework.restdocs.operation; +import org.jspecify.annotations.Nullable; + import org.springframework.http.HttpHeaders; /** @@ -42,14 +44,14 @@ HttpHeadersHelper addIfAbsent(String name, String value) { return this; } - HttpHeadersHelper updateContentLengthHeaderIfPresent(byte[] content) { + HttpHeadersHelper updateContentLengthHeaderIfPresent(byte @Nullable [] content) { if (this.httpHeaders.getContentLength() != -1) { setContentLengthHeader(content); } return this; } - HttpHeadersHelper setContentLengthHeader(byte[] content) { + HttpHeadersHelper setContentLengthHeader(byte @Nullable [] content) { if (content == null || content.length == 0) { this.httpHeaders.remove(HttpHeaders.CONTENT_LENGTH); } diff --git a/spring-restdocs-core/src/main/java/org/springframework/restdocs/operation/OperationRequestFactory.java b/spring-restdocs-core/src/main/java/org/springframework/restdocs/operation/OperationRequestFactory.java index acb31a1d..d1dea6b3 100644 --- a/spring-restdocs-core/src/main/java/org/springframework/restdocs/operation/OperationRequestFactory.java +++ b/spring-restdocs-core/src/main/java/org/springframework/restdocs/operation/OperationRequestFactory.java @@ -20,6 +20,8 @@ import java.util.Collection; import java.util.Collections; +import org.jspecify.annotations.Nullable; + import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; @@ -43,8 +45,8 @@ public class OperationRequestFactory { * @return the {@code OperationRequest} * @since 3.0.0 */ - public OperationRequest create(URI uri, HttpMethod method, byte[] content, HttpHeaders headers, - Collection parts, Collection cookies) { + public OperationRequest create(URI uri, HttpMethod method, byte @Nullable [] content, HttpHeaders headers, + @Nullable Collection parts, Collection cookies) { return new StandardOperationRequest(uri, method, content, augmentHeaders(headers, uri, content), (parts != null) ? parts : Collections.emptyList(), cookies); } @@ -61,7 +63,7 @@ public OperationRequest create(URI uri, HttpMethod method, byte[] content, HttpH * @return the {@code OperationRequest} * @since 3.0.0 */ - public OperationRequest create(URI uri, HttpMethod method, byte[] content, HttpHeaders headers, + public OperationRequest create(URI uri, HttpMethod method, byte @Nullable [] content, HttpHeaders headers, Collection parts) { return create(uri, method, content, headers, parts, Collections.emptyList()); } @@ -74,7 +76,7 @@ public OperationRequest create(URI uri, HttpMethod method, byte[] content, HttpH * @param newContent the new content * @return the new request with the new content */ - public OperationRequest createFrom(OperationRequest original, byte[] newContent) { + public OperationRequest createFrom(OperationRequest original, byte @Nullable [] newContent) { return new StandardOperationRequest(original.getUri(), original.getMethod(), newContent, getUpdatedHeaders(original.getHeaders(), newContent), original.getParts(), original.getCookies()); } @@ -91,7 +93,7 @@ public OperationRequest createFrom(OperationRequest original, HttpHeaders newHea original.getParts(), original.getCookies()); } - private HttpHeaders augmentHeaders(HttpHeaders originalHeaders, URI uri, byte[] content) { + private HttpHeaders augmentHeaders(HttpHeaders originalHeaders, URI uri, byte @Nullable [] content) { return new HttpHeadersHelper(originalHeaders).addIfAbsent(HttpHeaders.HOST, createHostHeader(uri)) .setContentLengthHeader(content) .getHeaders(); @@ -104,7 +106,7 @@ private String createHostHeader(URI uri) { return uri.getHost() + ":" + uri.getPort(); } - private HttpHeaders getUpdatedHeaders(HttpHeaders originalHeaders, byte[] updatedContent) { + private HttpHeaders getUpdatedHeaders(HttpHeaders originalHeaders, byte @Nullable [] updatedContent) { return new HttpHeadersHelper(originalHeaders).updateContentLengthHeaderIfPresent(updatedContent).getHeaders(); } diff --git a/spring-restdocs-core/src/main/java/org/springframework/restdocs/operation/OperationRequestPart.java b/spring-restdocs-core/src/main/java/org/springframework/restdocs/operation/OperationRequestPart.java index bd41ce1a..8e7cf7e1 100644 --- a/spring-restdocs-core/src/main/java/org/springframework/restdocs/operation/OperationRequestPart.java +++ b/spring-restdocs-core/src/main/java/org/springframework/restdocs/operation/OperationRequestPart.java @@ -16,6 +16,8 @@ package org.springframework.restdocs.operation; +import org.jspecify.annotations.Nullable; + import org.springframework.http.HttpHeaders; /** @@ -34,9 +36,9 @@ public interface OperationRequestPart { /** * Returns the name of the file that is being uploaded in this part. - * @return the name of the file + * @return the name of the file, or {@code null} if the part has no file name */ - String getSubmittedFileName(); + @Nullable String getSubmittedFileName(); /** * Returns the contents of the part. diff --git a/spring-restdocs-core/src/main/java/org/springframework/restdocs/operation/OperationRequestPartFactory.java b/spring-restdocs-core/src/main/java/org/springframework/restdocs/operation/OperationRequestPartFactory.java index 8064e64f..4ff936c8 100644 --- a/spring-restdocs-core/src/main/java/org/springframework/restdocs/operation/OperationRequestPartFactory.java +++ b/spring-restdocs-core/src/main/java/org/springframework/restdocs/operation/OperationRequestPartFactory.java @@ -16,6 +16,8 @@ package org.springframework.restdocs.operation; +import org.jspecify.annotations.Nullable; + import org.springframework.http.HttpHeaders; /** @@ -35,7 +37,8 @@ public class OperationRequestPartFactory { * @param headers the headers of the part * @return the {@code OperationRequestPart} */ - public OperationRequestPart create(String name, String submittedFileName, byte[] content, HttpHeaders headers) { + public OperationRequestPart create(String name, @Nullable String submittedFileName, byte[] content, + HttpHeaders headers) { return new StandardOperationRequestPart(name, submittedFileName, content, augmentHeaders(headers, content)); } diff --git a/spring-restdocs-core/src/main/java/org/springframework/restdocs/operation/OperationResponseFactory.java b/spring-restdocs-core/src/main/java/org/springframework/restdocs/operation/OperationResponseFactory.java index cb79a686..c3fd617e 100644 --- a/spring-restdocs-core/src/main/java/org/springframework/restdocs/operation/OperationResponseFactory.java +++ b/spring-restdocs-core/src/main/java/org/springframework/restdocs/operation/OperationResponseFactory.java @@ -19,6 +19,8 @@ import java.util.Collection; import java.util.Collections; +import org.jspecify.annotations.Nullable; + import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatusCode; @@ -40,7 +42,7 @@ public class OperationResponseFactory { * @return the {@code OperationResponse} * @since 3.0.0 */ - public OperationResponse create(HttpStatusCode status, HttpHeaders headers, byte[] content) { + public OperationResponse create(HttpStatusCode status, HttpHeaders headers, byte @Nullable [] content) { return new StandardOperationResponse(status, augmentHeaders(headers, content), content, Collections.emptyList()); } @@ -56,7 +58,7 @@ public OperationResponse create(HttpStatusCode status, HttpHeaders headers, byte * @return the {@code OperationResponse} * @since 3.0.0 */ - public OperationResponse create(HttpStatusCode status, HttpHeaders headers, byte[] content, + public OperationResponse create(HttpStatusCode status, HttpHeaders headers, byte @Nullable [] content, Collection cookies) { return new StandardOperationResponse(status, augmentHeaders(headers, content), content, cookies); } @@ -87,7 +89,7 @@ public OperationResponse createFrom(OperationResponse original, HttpHeaders newH original.getCookies()); } - private HttpHeaders augmentHeaders(HttpHeaders originalHeaders, byte[] content) { + private HttpHeaders augmentHeaders(HttpHeaders originalHeaders, byte @Nullable [] content) { return new HttpHeadersHelper(originalHeaders).setContentLengthHeader(content).getHeaders(); } diff --git a/spring-restdocs-core/src/main/java/org/springframework/restdocs/operation/StandardOperationRequest.java b/spring-restdocs-core/src/main/java/org/springframework/restdocs/operation/StandardOperationRequest.java index ae4d47d7..cce4276a 100644 --- a/spring-restdocs-core/src/main/java/org/springframework/restdocs/operation/StandardOperationRequest.java +++ b/spring-restdocs-core/src/main/java/org/springframework/restdocs/operation/StandardOperationRequest.java @@ -20,6 +20,8 @@ import java.util.Collection; import java.util.Collections; +import org.jspecify.annotations.Nullable; + import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; @@ -49,7 +51,7 @@ class StandardOperationRequest extends AbstractOperationMessage implements Opera * @param parts the parts * @param cookies the cookies */ - StandardOperationRequest(URI uri, HttpMethod method, byte[] content, HttpHeaders headers, + StandardOperationRequest(URI uri, HttpMethod method, byte @Nullable [] content, HttpHeaders headers, Collection parts, Collection cookies) { super(content, headers); this.uri = uri; diff --git a/spring-restdocs-core/src/main/java/org/springframework/restdocs/operation/StandardOperationRequestPart.java b/spring-restdocs-core/src/main/java/org/springframework/restdocs/operation/StandardOperationRequestPart.java index bf63a0e9..c444cd6f 100644 --- a/spring-restdocs-core/src/main/java/org/springframework/restdocs/operation/StandardOperationRequestPart.java +++ b/spring-restdocs-core/src/main/java/org/springframework/restdocs/operation/StandardOperationRequestPart.java @@ -16,6 +16,8 @@ package org.springframework.restdocs.operation; +import org.jspecify.annotations.Nullable; + import org.springframework.http.HttpHeaders; /** @@ -27,7 +29,7 @@ class StandardOperationRequestPart extends AbstractOperationMessage implements O private final String name; - private final String submittedFileName; + private final @Nullable String submittedFileName; /** * Creates a new {@code StandardOperationRequestPart} with the given {@code name}. @@ -36,7 +38,7 @@ class StandardOperationRequestPart extends AbstractOperationMessage implements O * @param content the contents of the part * @param headers the headers of the part */ - StandardOperationRequestPart(String name, String submittedFileName, byte[] content, HttpHeaders headers) { + StandardOperationRequestPart(String name, @Nullable String submittedFileName, byte[] content, HttpHeaders headers) { super(content, headers); this.name = name; this.submittedFileName = submittedFileName; @@ -48,7 +50,7 @@ public String getName() { } @Override - public String getSubmittedFileName() { + public @Nullable String getSubmittedFileName() { return this.submittedFileName; } diff --git a/spring-restdocs-core/src/main/java/org/springframework/restdocs/operation/StandardOperationResponse.java b/spring-restdocs-core/src/main/java/org/springframework/restdocs/operation/StandardOperationResponse.java index c6fa966e..e1ba5155 100644 --- a/spring-restdocs-core/src/main/java/org/springframework/restdocs/operation/StandardOperationResponse.java +++ b/spring-restdocs-core/src/main/java/org/springframework/restdocs/operation/StandardOperationResponse.java @@ -18,6 +18,8 @@ import java.util.Collection; +import org.jspecify.annotations.Nullable; + import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatusCode; @@ -41,7 +43,7 @@ class StandardOperationResponse extends AbstractOperationMessage implements Oper * @param content the content of the response * @param cookies any cookies included in the response */ - StandardOperationResponse(HttpStatusCode status, HttpHeaders headers, byte[] content, + StandardOperationResponse(HttpStatusCode status, HttpHeaders headers, byte @Nullable [] content, Collection cookies) { super(content, headers); this.status = status; diff --git a/spring-restdocs-core/src/main/java/org/springframework/restdocs/operation/package-info.java b/spring-restdocs-core/src/main/java/org/springframework/restdocs/operation/package-info.java index 26132c19..0fcd3fec 100644 --- a/spring-restdocs-core/src/main/java/org/springframework/restdocs/operation/package-info.java +++ b/spring-restdocs-core/src/main/java/org/springframework/restdocs/operation/package-info.java @@ -18,4 +18,7 @@ * Operation API that describes a request that was sent and the response that was received * when calling a RESTful API. */ +@NullMarked package org.springframework.restdocs.operation; + +import org.jspecify.annotations.NullMarked; diff --git a/spring-restdocs-core/src/main/java/org/springframework/restdocs/operation/preprocess/ContentModifier.java b/spring-restdocs-core/src/main/java/org/springframework/restdocs/operation/preprocess/ContentModifier.java index f71e9f26..882e9ac4 100644 --- a/spring-restdocs-core/src/main/java/org/springframework/restdocs/operation/preprocess/ContentModifier.java +++ b/spring-restdocs-core/src/main/java/org/springframework/restdocs/operation/preprocess/ContentModifier.java @@ -16,6 +16,8 @@ package org.springframework.restdocs.operation.preprocess; +import org.jspecify.annotations.Nullable; + import org.springframework.http.MediaType; import org.springframework.restdocs.operation.OperationRequest; import org.springframework.restdocs.operation.OperationResponse; @@ -36,6 +38,6 @@ public interface ContentModifier { * @param contentType the type of the original content, may be {@code null} * @return the modified content */ - byte[] modifyContent(byte[] originalContent, MediaType contentType); + byte[] modifyContent(byte[] originalContent, @Nullable MediaType contentType); } diff --git a/spring-restdocs-core/src/main/java/org/springframework/restdocs/operation/preprocess/HeadersModifyingOperationPreprocessor.java b/spring-restdocs-core/src/main/java/org/springframework/restdocs/operation/preprocess/HeadersModifyingOperationPreprocessor.java index fbcc21f5..770c2a7e 100644 --- a/spring-restdocs-core/src/main/java/org/springframework/restdocs/operation/preprocess/HeadersModifyingOperationPreprocessor.java +++ b/spring-restdocs-core/src/main/java/org/springframework/restdocs/operation/preprocess/HeadersModifyingOperationPreprocessor.java @@ -210,7 +210,7 @@ private RemoveHeadersByNamePatternModification(Pattern namePattern) { @Override public void applyTo(HttpHeaders headers) { - headers.keySet().removeIf((name) -> this.namePattern.matcher(name).matches()); + headers.headerNames().removeIf((name) -> this.namePattern.matcher(name).matches()); } } diff --git a/spring-restdocs-core/src/main/java/org/springframework/restdocs/operation/preprocess/LinkMaskingContentModifier.java b/spring-restdocs-core/src/main/java/org/springframework/restdocs/operation/preprocess/LinkMaskingContentModifier.java index d3cf1667..e618be6e 100644 --- a/spring-restdocs-core/src/main/java/org/springframework/restdocs/operation/preprocess/LinkMaskingContentModifier.java +++ b/spring-restdocs-core/src/main/java/org/springframework/restdocs/operation/preprocess/LinkMaskingContentModifier.java @@ -19,6 +19,8 @@ import java.nio.charset.StandardCharsets; import java.util.regex.Pattern; +import org.jspecify.annotations.Nullable; + import org.springframework.http.MediaType; /** @@ -43,7 +45,7 @@ class LinkMaskingContentModifier implements ContentModifier { } @Override - public byte[] modifyContent(byte[] originalContent, MediaType contentType) { + public byte[] modifyContent(byte[] originalContent, @Nullable MediaType contentType) { return this.contentModifier.modifyContent(originalContent, contentType); } diff --git a/spring-restdocs-core/src/main/java/org/springframework/restdocs/operation/preprocess/PatternReplacingContentModifier.java b/spring-restdocs-core/src/main/java/org/springframework/restdocs/operation/preprocess/PatternReplacingContentModifier.java index 42ad2ead..336518eb 100644 --- a/spring-restdocs-core/src/main/java/org/springframework/restdocs/operation/preprocess/PatternReplacingContentModifier.java +++ b/spring-restdocs-core/src/main/java/org/springframework/restdocs/operation/preprocess/PatternReplacingContentModifier.java @@ -20,6 +20,8 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; +import org.jspecify.annotations.Nullable; + import org.springframework.http.MediaType; /** @@ -65,7 +67,7 @@ class PatternReplacingContentModifier implements ContentModifier { } @Override - public byte[] modifyContent(byte[] content, MediaType contentType) { + public byte[] modifyContent(byte[] content, @Nullable MediaType contentType) { Charset charset = (contentType != null && contentType.getCharset() != null) ? contentType.getCharset() : this.fallbackCharset; String original = new String(content, charset); diff --git a/spring-restdocs-core/src/main/java/org/springframework/restdocs/operation/preprocess/Preprocessors.java b/spring-restdocs-core/src/main/java/org/springframework/restdocs/operation/preprocess/Preprocessors.java index 6d6e0fd7..0f0eaab3 100644 --- a/spring-restdocs-core/src/main/java/org/springframework/restdocs/operation/preprocess/Preprocessors.java +++ b/spring-restdocs-core/src/main/java/org/springframework/restdocs/operation/preprocess/Preprocessors.java @@ -68,44 +68,6 @@ public static OperationPreprocessor prettyPrint() { return new ContentModifyingOperationPreprocessor(new PrettyPrintingContentModifier()); } - /** - * Returns an {@code OperationPreprocessor} that will remove any header from the - * request or response with a name that is equal to one of the given - * {@code headersToRemove}. - * @param headerNames the header names - * @return the preprocessor - * @deprecated since 3.0.0 in favor of {@link #modifyHeaders()} and - * {@link HeadersModifyingOperationPreprocessor#remove(String)} - * @see String#equals(Object) - */ - @Deprecated - public static OperationPreprocessor removeHeaders(String... headerNames) { - HeadersModifyingOperationPreprocessor preprocessor = new HeadersModifyingOperationPreprocessor(); - for (String headerName : headerNames) { - preprocessor.remove(headerName); - } - return preprocessor; - } - - /** - * Returns an {@code OperationPreprocessor} that will remove any headers from the - * request or response with a name that matches one of the given - * {@code headerNamePatterns} regular expressions. - * @param headerNamePatterns the header name patterns - * @return the preprocessor - * @deprecated since 3.0.0 in favor of {@link #modifyHeaders()} and - * {@link HeadersModifyingOperationPreprocessor#removeMatching(String)} - * @see java.util.regex.Matcher#matches() - */ - @Deprecated - public static OperationPreprocessor removeMatchingHeaders(String... headerNamePatterns) { - HeadersModifyingOperationPreprocessor preprocessor = new HeadersModifyingOperationPreprocessor(); - for (String headerNamePattern : headerNamePatterns) { - preprocessor.removeMatching(headerNamePattern); - } - return preprocessor; - } - /** * Returns an {@code OperationPreprocessor} that will mask the href of hypermedia * links in the request or response. diff --git a/spring-restdocs-core/src/main/java/org/springframework/restdocs/operation/preprocess/PrettyPrintingContentModifier.java b/spring-restdocs-core/src/main/java/org/springframework/restdocs/operation/preprocess/PrettyPrintingContentModifier.java index 929e97a4..40d10404 100644 --- a/spring-restdocs-core/src/main/java/org/springframework/restdocs/operation/preprocess/PrettyPrintingContentModifier.java +++ b/spring-restdocs-core/src/main/java/org/springframework/restdocs/operation/preprocess/PrettyPrintingContentModifier.java @@ -34,14 +34,15 @@ import javax.xml.transform.sax.SAXSource; import javax.xml.transform.stream.StreamResult; -import com.fasterxml.jackson.databind.DeserializationFeature; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.SerializationFeature; +import org.jspecify.annotations.Nullable; import org.xml.sax.ErrorHandler; import org.xml.sax.InputSource; import org.xml.sax.SAXException; import org.xml.sax.SAXParseException; import org.xml.sax.XMLReader; +import tools.jackson.databind.ObjectMapper; +import tools.jackson.databind.SerializationFeature; +import tools.jackson.databind.json.JsonMapper; import org.springframework.http.MediaType; @@ -56,7 +57,7 @@ public class PrettyPrintingContentModifier implements ContentModifier { .unmodifiableList(Arrays.asList(new JsonPrettyPrinter(), new XmlPrettyPrinter())); @Override - public byte[] modifyContent(byte[] originalContent, MediaType contentType) { + public byte[] modifyContent(byte[] originalContent, @Nullable MediaType contentType) { if (originalContent.length > 0) { for (PrettyPrinter prettyPrinter : PRETTY_PRINTERS) { try { @@ -141,8 +142,9 @@ public void fatalError(SAXParseException exception) throws SAXException { private static final class JsonPrettyPrinter implements PrettyPrinter { - private final ObjectMapper objectMapper = new ObjectMapper().configure(SerializationFeature.INDENT_OUTPUT, true) - .configure(DeserializationFeature.FAIL_ON_TRAILING_TOKENS, true); + private final ObjectMapper objectMapper = JsonMapper.builder() + .enable(SerializationFeature.INDENT_OUTPUT) + .build(); @Override public byte[] prettyPrint(byte[] original) throws IOException { diff --git a/spring-restdocs-core/src/main/java/org/springframework/restdocs/operation/preprocess/UriModifyingOperationPreprocessor.java b/spring-restdocs-core/src/main/java/org/springframework/restdocs/operation/preprocess/UriModifyingOperationPreprocessor.java index 04bf32f7..19516890 100644 --- a/spring-restdocs-core/src/main/java/org/springframework/restdocs/operation/preprocess/UriModifyingOperationPreprocessor.java +++ b/spring-restdocs-core/src/main/java/org/springframework/restdocs/operation/preprocess/UriModifyingOperationPreprocessor.java @@ -25,6 +25,8 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; +import org.jspecify.annotations.Nullable; + import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; import org.springframework.restdocs.operation.OperationRequest; @@ -60,11 +62,11 @@ public class UriModifyingOperationPreprocessor implements OperationPreprocessor private final OperationPreprocessor contentModifyingDelegate = new ContentModifyingOperationPreprocessor( this.contentModifier); - private String scheme; + private @Nullable String scheme; - private String host; + private @Nullable String host; - private String port; + private @Nullable String port; /** * Modifies the URI to use the given {@code scheme}. {@code null}, the default, will @@ -72,7 +74,7 @@ public class UriModifyingOperationPreprocessor implements OperationPreprocessor * @param scheme the scheme * @return {@code this} */ - public UriModifyingOperationPreprocessor scheme(String scheme) { + public UriModifyingOperationPreprocessor scheme(@Nullable String scheme) { this.scheme = scheme; this.contentModifier.setScheme(scheme); return this; @@ -84,7 +86,7 @@ public UriModifyingOperationPreprocessor scheme(String scheme) { * @param host the host * @return {@code this} */ - public UriModifyingOperationPreprocessor host(String host) { + public UriModifyingOperationPreprocessor host(@Nullable String host) { this.host = host; this.contentModifier.setHost(host); return this; @@ -147,7 +149,7 @@ public OperationResponse preprocess(OperationResponse response) { private HttpHeaders modify(HttpHeaders headers) { HttpHeaders modified = new HttpHeaders(); - for (Entry> header : headers.entrySet()) { + for (Entry> header : headers.headerSet()) { for (String value : header.getValue()) { modified.add(header.getKey(), this.contentModifier.modify(value)); } @@ -171,26 +173,26 @@ private static final class UriModifyingContentModifier implements ContentModifie private static final Pattern SCHEME_HOST_PORT_PATTERN = Pattern .compile("(http[s]?)://([a-zA-Z0-9-\\.]+)(:[0-9]+)?"); - private String scheme; + private @Nullable String scheme; - private String host; + private @Nullable String host; - private String port; + private @Nullable String port; - private void setScheme(String scheme) { + private void setScheme(@Nullable String scheme) { this.scheme = scheme; } - private void setHost(String host) { + private void setHost(@Nullable String host) { this.host = host; } - private void setPort(String port) { + private void setPort(@Nullable String port) { this.port = port; } @Override - public byte[] modifyContent(byte[] content, MediaType contentType) { + public byte[] modifyContent(byte[] content, @Nullable MediaType contentType) { String input; if (contentType != null && contentType.getCharset() != null) { input = new String(content, contentType.getCharset()); diff --git a/spring-restdocs-core/src/main/java/org/springframework/restdocs/operation/preprocess/package-info.java b/spring-restdocs-core/src/main/java/org/springframework/restdocs/operation/preprocess/package-info.java index f00fe104..c559fe6a 100644 --- a/spring-restdocs-core/src/main/java/org/springframework/restdocs/operation/preprocess/package-info.java +++ b/spring-restdocs-core/src/main/java/org/springframework/restdocs/operation/preprocess/package-info.java @@ -17,4 +17,7 @@ /** * Support for preprocessing an operation prior to it being documented. */ +@NullMarked package org.springframework.restdocs.operation.preprocess; + +import org.jspecify.annotations.NullMarked; diff --git a/spring-restdocs-core/src/main/java/org/springframework/restdocs/package-info.java b/spring-restdocs-core/src/main/java/org/springframework/restdocs/package-info.java index b04b0d0e..f074229d 100644 --- a/spring-restdocs-core/src/main/java/org/springframework/restdocs/package-info.java +++ b/spring-restdocs-core/src/main/java/org/springframework/restdocs/package-info.java @@ -17,4 +17,7 @@ /** * Core Spring REST Docs classes. */ +@NullMarked package org.springframework.restdocs; + +import org.jspecify.annotations.NullMarked; diff --git a/spring-restdocs-core/src/main/java/org/springframework/restdocs/payload/AbstractBodySnippet.java b/spring-restdocs-core/src/main/java/org/springframework/restdocs/payload/AbstractBodySnippet.java index 88091520..f7637757 100644 --- a/spring-restdocs-core/src/main/java/org/springframework/restdocs/payload/AbstractBodySnippet.java +++ b/spring-restdocs-core/src/main/java/org/springframework/restdocs/payload/AbstractBodySnippet.java @@ -21,6 +21,8 @@ import java.util.HashMap; import java.util.Map; +import org.jspecify.annotations.Nullable; + import org.springframework.http.MediaType; import org.springframework.restdocs.operation.Operation; import org.springframework.restdocs.snippet.ModelCreationException; @@ -35,20 +37,21 @@ */ public abstract class AbstractBodySnippet extends TemplatedSnippet { - private final PayloadSubsectionExtractor subsectionExtractor; + private final @Nullable PayloadSubsectionExtractor subsectionExtractor; /** * Creates a new {@code AbstractBodySnippet} that will produce a snippet named * {@code -body} using a template named {@code -body}. The snippet will * contain the subsection of the body extracted by the given - * {@code subsectionExtractor}. The given {@code attributes} will be included in the - * model during template rendering + * {@code subsectionExtractor}. If the extractor is {@code null}, the snippet will + * contain the entire body. The given {@code attributes} will be included in the model + * during template rendering * @param type the type of the body * @param subsectionExtractor the subsection extractor * @param attributes the attributes */ - protected AbstractBodySnippet(String type, PayloadSubsectionExtractor subsectionExtractor, - Map attributes) { + protected AbstractBodySnippet(String type, @Nullable PayloadSubsectionExtractor subsectionExtractor, + @Nullable Map attributes) { this(type, type, subsectionExtractor, attributes); } @@ -56,15 +59,16 @@ protected AbstractBodySnippet(String type, PayloadSubsectionExtractor subsect * Creates a new {@code AbstractBodySnippet} that will produce a snippet named * {@code -body} using a template named {@code -body}. The snippet will * contain the subsection of the body extracted by the given - * {@code subsectionExtractor}. The given {@code attributes} will be included in the - * model during template rendering + * {@code subsectionExtractor}. If the extractor is {@code null}, the snippet will + * contain the entire body. The given {@code attributes} will be included in the model + * during template rendering * @param name the name of the snippet * @param type the type of the body * @param subsectionExtractor the subsection extractor * @param attributes the attributes */ - protected AbstractBodySnippet(String name, String type, PayloadSubsectionExtractor subsectionExtractor, - Map attributes) { + protected AbstractBodySnippet(String name, String type, @Nullable PayloadSubsectionExtractor subsectionExtractor, + @Nullable Map attributes) { super(name + "-body" + ((subsectionExtractor != null) ? "-" + subsectionExtractor.getSubsectionId() : ""), type + "-body", attributes); this.subsectionExtractor = subsectionExtractor; @@ -91,14 +95,14 @@ protected Map createModel(Operation operation) { } } - private String determineLanguage(MediaType contentType) { + private @Nullable String determineLanguage(@Nullable MediaType contentType) { if (contentType == null) { return null; } return (contentType.getSubtypeSuffix() != null) ? contentType.getSubtypeSuffix() : contentType.getSubtype(); } - private Charset extractCharset(MediaType contentType) { + private @Nullable Charset extractCharset(@Nullable MediaType contentType) { if (contentType == null) { return null; } @@ -120,6 +124,6 @@ private Charset extractCharset(MediaType contentType) { * @param operation the operation * @return the content type */ - protected abstract MediaType getContentType(Operation operation); + protected abstract @Nullable MediaType getContentType(Operation operation); } diff --git a/spring-restdocs-core/src/main/java/org/springframework/restdocs/payload/AbstractFieldsSnippet.java b/spring-restdocs-core/src/main/java/org/springframework/restdocs/payload/AbstractFieldsSnippet.java index 0a10b899..67360bd8 100644 --- a/spring-restdocs-core/src/main/java/org/springframework/restdocs/payload/AbstractFieldsSnippet.java +++ b/spring-restdocs-core/src/main/java/org/springframework/restdocs/payload/AbstractFieldsSnippet.java @@ -22,6 +22,8 @@ import java.util.List; import java.util.Map; +import org.jspecify.annotations.Nullable; + import org.springframework.http.MediaType; import org.springframework.restdocs.operation.Operation; import org.springframework.restdocs.snippet.Attributes; @@ -48,7 +50,7 @@ public abstract class AbstractFieldsSnippet extends TemplatedSnippet { private final String type; - private final PayloadSubsectionExtractor subsectionExtractor; + private final @Nullable PayloadSubsectionExtractor subsectionExtractor; /** * Creates a new {@code AbstractFieldsSnippet} that will produce a snippet named @@ -62,8 +64,8 @@ public abstract class AbstractFieldsSnippet extends TemplatedSnippet { * @param attributes the additional attributes * @param ignoreUndocumentedFields whether undocumented fields should be ignored */ - protected AbstractFieldsSnippet(String type, List descriptors, Map attributes, - boolean ignoreUndocumentedFields) { + protected AbstractFieldsSnippet(String type, List descriptors, + @Nullable Map attributes, boolean ignoreUndocumentedFields) { this(type, type, descriptors, attributes, ignoreUndocumentedFields); } @@ -71,7 +73,8 @@ protected AbstractFieldsSnippet(String type, List descriptors, * Creates a new {@code AbstractFieldsSnippet} that will produce a snippet named * {@code -fields} using a template named {@code -fields}. The fields in * the subsection of the payload extracted by the given {@code subsectionExtractor} - * will be documented using the given {@code descriptors} and the given + * will be documented using the given {@code descriptors}. If the extractor is + * {@code null}, the fields of the entire payload will be documented. The given * {@code attributes} will be included in the model during template rendering. If * {@code ignoreUndocumentedFields} is {@code true}, undocumented fields will be * ignored and will not trigger a failure. @@ -79,11 +82,13 @@ protected AbstractFieldsSnippet(String type, List descriptors, * @param descriptors the field descriptors * @param attributes the additional attributes * @param ignoreUndocumentedFields whether undocumented fields should be ignored - * @param subsectionExtractor the subsection extractor + * @param subsectionExtractor the subsection extractor or {@code null} to document the + * fields of the entire payload * @since 1.2.0 */ - protected AbstractFieldsSnippet(String type, List descriptors, Map attributes, - boolean ignoreUndocumentedFields, PayloadSubsectionExtractor subsectionExtractor) { + protected AbstractFieldsSnippet(String type, List descriptors, + @Nullable Map attributes, boolean ignoreUndocumentedFields, + @Nullable PayloadSubsectionExtractor subsectionExtractor) { this(type, type, descriptors, attributes, ignoreUndocumentedFields, subsectionExtractor); } @@ -101,16 +106,17 @@ protected AbstractFieldsSnippet(String type, List descriptors, * @param ignoreUndocumentedFields whether undocumented fields should be ignored */ protected AbstractFieldsSnippet(String name, String type, List descriptors, - Map attributes, boolean ignoreUndocumentedFields) { + @Nullable Map attributes, boolean ignoreUndocumentedFields) { this(name, type, descriptors, attributes, ignoreUndocumentedFields, null); } /** * Creates a new {@code AbstractFieldsSnippet} that will produce a snippet named * {@code -fields} using a template named {@code -fields}. The fields in - * the subsection of the payload identified by {@code subsectionPath} will be - * documented using the given {@code descriptors} and the given {@code attributes} - * will be included in the model during template rendering. If + * the subsection of the payload extracted by the given {@code subsectionExtractor} + * will be documented using the given {@code descriptors}. If the extractor is + * {@code null}, the fields of the entire payload will be documented. The given + * {@code attributes} will be included in the model during template rendering. If * {@code ignoreUndocumentedFields} is {@code true}, undocumented fields will be * ignored and will not trigger a failure. * @param name the name of the snippet @@ -118,13 +124,13 @@ protected AbstractFieldsSnippet(String name, String type, List * @param descriptors the field descriptors * @param attributes the additional attributes * @param ignoreUndocumentedFields whether undocumented fields should be ignored - * @param subsectionExtractor the subsection extractor documented. {@code null} or an - * empty string can be used to indicate that the entire payload should be documented. + * @param subsectionExtractor the subsection extractor or {@code null} to document the + * fields of the entire payload. * @since 1.2.0 */ protected AbstractFieldsSnippet(String name, String type, List descriptors, - Map attributes, boolean ignoreUndocumentedFields, - PayloadSubsectionExtractor subsectionExtractor) { + @Nullable Map attributes, boolean ignoreUndocumentedFields, + @Nullable PayloadSubsectionExtractor subsectionExtractor) { super(name + "-fields" + ((subsectionExtractor != null) ? "-" + subsectionExtractor.getSubsectionId() : ""), type + "-fields", attributes); for (FieldDescriptor descriptor : descriptors) { @@ -225,7 +231,7 @@ private void validateFieldDocumentation(ContentHandler payloadHandler) { * @param operation the operation * @return the content type */ - protected abstract MediaType getContentType(Operation operation); + protected abstract @Nullable MediaType getContentType(Operation operation); /** * Returns the content of the request or response extracted form the given @@ -258,7 +264,7 @@ protected final boolean isIgnoredUndocumentedFields() { * @return the subsection extractor or {@code null} * @since 1.2.4 */ - protected final PayloadSubsectionExtractor getSubsectionExtractor() { + protected final @Nullable PayloadSubsectionExtractor getSubsectionExtractor() { return this.subsectionExtractor; } @@ -270,7 +276,10 @@ protected final PayloadSubsectionExtractor getSubsectionExtractor() { protected Map createModelForDescriptor(FieldDescriptor descriptor) { Map model = new HashMap<>(); model.put("path", descriptor.getPath()); - model.put("type", descriptor.getType().toString()); + Object type = descriptor.getType(); + Assert.notNull(type, + () -> "Field with path '" + descriptor.getPath() + "' cannot be documented as its type is unknown"); + model.put("type", type.toString()); model.put("description", descriptor.getDescription()); model.put("optional", descriptor.isOptional()); model.putAll(descriptor.getAttributes()); diff --git a/spring-restdocs-core/src/main/java/org/springframework/restdocs/payload/ContentHandler.java b/spring-restdocs-core/src/main/java/org/springframework/restdocs/payload/ContentHandler.java index 8c602da0..0ec75602 100644 --- a/spring-restdocs-core/src/main/java/org/springframework/restdocs/payload/ContentHandler.java +++ b/spring-restdocs-core/src/main/java/org/springframework/restdocs/payload/ContentHandler.java @@ -18,6 +18,8 @@ import java.util.List; +import org.jspecify.annotations.Nullable; + import org.springframework.http.MediaType; /** @@ -45,7 +47,7 @@ interface ContentHandler extends FieldTypeResolver { * documented * @throws PayloadHandlingException if a failure occurs */ - String getUndocumentedContent(); + @Nullable String getUndocumentedContent(); /** * Create a {@link ContentHandler} for the given content type and payload, described @@ -56,7 +58,7 @@ interface ContentHandler extends FieldTypeResolver { * @return the ContentHandler * @throws PayloadHandlingException if no known ContentHandler can handle the content */ - static ContentHandler forContentWithDescriptors(byte[] content, MediaType contentType, + static ContentHandler forContentWithDescriptors(byte[] content, @Nullable MediaType contentType, List descriptors) { try { return new JsonContentHandler(content, descriptors); @@ -66,8 +68,9 @@ static ContentHandler forContentWithDescriptors(byte[] content, MediaType conten return new XmlContentHandler(content, descriptors); } catch (Exception xe) { - throw new PayloadHandlingException( - "Cannot handle " + contentType + " content as it could not be parsed as JSON or XML"); + throw new PayloadHandlingException("Cannot handle content " + + ((contentType != null) ? "with type " + contentType : "of unknown type") + + " as it could not be parsed as JSON or XML"); } } } diff --git a/spring-restdocs-core/src/main/java/org/springframework/restdocs/payload/FieldDescriptor.java b/spring-restdocs-core/src/main/java/org/springframework/restdocs/payload/FieldDescriptor.java index abf1480b..ee4bb505 100644 --- a/spring-restdocs-core/src/main/java/org/springframework/restdocs/payload/FieldDescriptor.java +++ b/spring-restdocs-core/src/main/java/org/springframework/restdocs/payload/FieldDescriptor.java @@ -16,7 +16,10 @@ package org.springframework.restdocs.payload; +import org.jspecify.annotations.Nullable; + import org.springframework.restdocs.snippet.IgnorableDescriptor; +import org.springframework.util.Assert; /** * A description of a field found in a request or response payload. @@ -29,7 +32,7 @@ public class FieldDescriptor extends IgnorableDescriptor { private final String path; - private Object type; + private @Nullable Object type; private boolean optional; @@ -39,6 +42,7 @@ public class FieldDescriptor extends IgnorableDescriptor { * @param path the path */ protected FieldDescriptor(String path) { + Assert.notNull(path, "Path must not be null"); this.path = path; } @@ -49,7 +53,7 @@ protected FieldDescriptor(String path) { * @return {@code this} * @see JsonFieldType */ - public final FieldDescriptor type(Object type) { + public final FieldDescriptor type(@Nullable Object type) { this.type = type; return this; } @@ -75,7 +79,7 @@ public final String getPath() { * Returns the type of the field described by this descriptor. * @return the type */ - public final Object getType() { + public final @Nullable Object getType() { return this.type; } diff --git a/spring-restdocs-core/src/main/java/org/springframework/restdocs/payload/FieldPathPayloadSubsectionExtractor.java b/spring-restdocs-core/src/main/java/org/springframework/restdocs/payload/FieldPathPayloadSubsectionExtractor.java index a649cb80..ba83fc86 100644 --- a/spring-restdocs-core/src/main/java/org/springframework/restdocs/payload/FieldPathPayloadSubsectionExtractor.java +++ b/spring-restdocs-core/src/main/java/org/springframework/restdocs/payload/FieldPathPayloadSubsectionExtractor.java @@ -16,7 +16,6 @@ package org.springframework.restdocs.payload; -import java.io.IOException; import java.util.Collections; import java.util.List; import java.util.Map; @@ -24,8 +23,11 @@ import java.util.TreeSet; import java.util.stream.Collectors; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.SerializationFeature; +import org.jspecify.annotations.Nullable; +import tools.jackson.core.JacksonException; +import tools.jackson.databind.ObjectMapper; +import tools.jackson.databind.SerializationFeature; +import tools.jackson.databind.json.JsonMapper; import org.springframework.http.MediaType; import org.springframework.restdocs.payload.JsonFieldProcessor.ExtractedField; @@ -43,8 +45,9 @@ public class FieldPathPayloadSubsectionExtractor private static final ObjectMapper objectMapper = new ObjectMapper(); - private static final ObjectMapper prettyPrintingOjectMapper = new ObjectMapper() - .enable(SerializationFeature.INDENT_OUTPUT); + private static final ObjectMapper prettyPrintingOjectMapper = JsonMapper.builder() + .enable(SerializationFeature.INDENT_OUTPUT) + .build(); private final String fieldPath; @@ -73,12 +76,13 @@ protected FieldPathPayloadSubsectionExtractor(String fieldPath, String subsectio } @Override - public byte[] extractSubsection(byte[] payload, MediaType contentType) { + public byte[] extractSubsection(byte[] payload, @Nullable MediaType contentType) { return extractSubsection(payload, contentType, Collections.emptyList()); } @Override - public byte[] extractSubsection(byte[] payload, MediaType contentType, List descriptors) { + public byte[] extractSubsection(byte[] payload, @Nullable MediaType contentType, + List descriptors) { try { ExtractedField extractedField = new JsonFieldProcessor().extract(this.fieldPath, objectMapper.readValue(payload, Object.class)); @@ -122,7 +126,7 @@ public byte[] extractSubsection(byte[] payload, MediaType contentType, List map, String segment, Match parent) { + private MapMatch(Object item, Map map, String segment, @Nullable Match parent) { this.item = item; this.map = map; this.segment = segment; @@ -232,11 +234,11 @@ public void removeSubsection() { } } - private boolean isMapWithEntries(Object object) { + private boolean isMapWithEntries(@Nullable Object object) { return object instanceof Map && !((Map) object).isEmpty(); } - private boolean isCollectionWithNonScalarEntries(Object object) { + private boolean isCollectionWithNonScalarEntries(@Nullable Object object) { if (!(object instanceof Collection)) { return false; } @@ -258,9 +260,9 @@ private static final class CollectionMatch implements Match { private final Object item; - private final Match parent; + private final @Nullable Match parent; - private CollectionMatch(Iterator items, Collection collection, Object item, Match parent) { + private CollectionMatch(Iterator items, Collection collection, Object item, @Nullable Match parent) { this.items = items; this.collection = collection; this.item = item; @@ -309,9 +311,9 @@ private static final class LeafCollectionMatch implements Match { private final Collection collection; - private final Match parent; + private final @Nullable Match parent; - private LeafCollectionMatch(Collection collection, Match parent) { + private LeafCollectionMatch(Collection collection, @Nullable Match parent) { this.collection = collection; this.parent = parent; } @@ -375,7 +377,7 @@ private static final class ProcessingContext { private final List segments; - private final Match parent; + private final @Nullable Match parent; private final JsonFieldPath path; @@ -383,7 +385,8 @@ private ProcessingContext(Object payload, JsonFieldPath path) { this(payload, path, null, null); } - private ProcessingContext(Object payload, JsonFieldPath path, List segments, Match parent) { + private ProcessingContext(Object payload, JsonFieldPath path, @Nullable List segments, + @Nullable Match parent) { this.payload = payload; this.path = path; this.segments = (segments != null) ? segments : path.getSegments(); @@ -403,7 +406,7 @@ private boolean isLeaf() { return this.segments.size() == 1; } - private Match getParentMatch() { + private @Nullable Match getParentMatch() { return this.parent; } diff --git a/spring-restdocs-core/src/main/java/org/springframework/restdocs/payload/PayloadSubsectionExtractor.java b/spring-restdocs-core/src/main/java/org/springframework/restdocs/payload/PayloadSubsectionExtractor.java index 4dbc6a9e..f779b21c 100644 --- a/spring-restdocs-core/src/main/java/org/springframework/restdocs/payload/PayloadSubsectionExtractor.java +++ b/spring-restdocs-core/src/main/java/org/springframework/restdocs/payload/PayloadSubsectionExtractor.java @@ -18,6 +18,8 @@ import java.util.List; +import org.jspecify.annotations.Nullable; + import org.springframework.http.MediaType; /** @@ -36,7 +38,7 @@ public interface PayloadSubsectionExtractor descriptors) { + default byte[] extractSubsection(byte[] payload, @Nullable MediaType contentType, + List descriptors) { return extractSubsection(payload, contentType); } diff --git a/spring-restdocs-core/src/main/java/org/springframework/restdocs/payload/RequestBodySnippet.java b/spring-restdocs-core/src/main/java/org/springframework/restdocs/payload/RequestBodySnippet.java index c526118f..72a8cb38 100644 --- a/spring-restdocs-core/src/main/java/org/springframework/restdocs/payload/RequestBodySnippet.java +++ b/spring-restdocs-core/src/main/java/org/springframework/restdocs/payload/RequestBodySnippet.java @@ -19,6 +19,8 @@ import java.io.IOException; import java.util.Map; +import org.jspecify.annotations.Nullable; + import org.springframework.http.MediaType; import org.springframework.restdocs.operation.Operation; import org.springframework.restdocs.snippet.Snippet; @@ -39,10 +41,12 @@ public RequestBodySnippet() { /** * Creates a new {@code RequestBodySnippet} that will document the subsection of the - * request body extracted by the given {@code subsectionExtractor}. - * @param subsectionExtractor the subsection extractor + * request body extracted by the given {@code subsectionExtractor}. If the extractor + * is {@code null}, the fields of the entire payload will be documented. + * @param subsectionExtractor the subsection extractor, or {@code null} to document + * the entire body. */ - public RequestBodySnippet(PayloadSubsectionExtractor subsectionExtractor) { + public RequestBodySnippet(@Nullable PayloadSubsectionExtractor subsectionExtractor) { this(subsectionExtractor, null); } @@ -51,19 +55,23 @@ public RequestBodySnippet(PayloadSubsectionExtractor subsectionExtractor) { * {@code attributes} that will be included in the model during template rendering. * @param attributes the additional attributes */ - public RequestBodySnippet(Map attributes) { + public RequestBodySnippet(@Nullable Map attributes) { this(null, attributes); } /** * Creates a new {@code RequestBodySnippet} that will document the subsection of the - * request body extracted by the given {@code subsectionExtractor}. The given - * additional {@code attributes} that will be included in the model during template - * rendering. - * @param subsectionExtractor the subsection extractor + * request body extracted by the given {@code subsectionExtractor}. If the extractor + * is {@code null}, the entire body will be documented. + * + * The given additional {@code attributes} that will be included in the model during + * template rendering. + * @param subsectionExtractor the subsection extractor or {@code null} to document the + * entire body * @param attributes the additional attributes */ - public RequestBodySnippet(PayloadSubsectionExtractor subsectionExtractor, Map attributes) { + public RequestBodySnippet(@Nullable PayloadSubsectionExtractor subsectionExtractor, + @Nullable Map attributes) { super("request", subsectionExtractor, attributes); } @@ -73,7 +81,7 @@ protected byte[] getContent(Operation operation) throws IOException { } @Override - protected MediaType getContentType(Operation operation) { + protected @Nullable MediaType getContentType(Operation operation) { return operation.getRequest().getHeaders().getContentType(); } diff --git a/spring-restdocs-core/src/main/java/org/springframework/restdocs/payload/RequestFieldsSnippet.java b/spring-restdocs-core/src/main/java/org/springframework/restdocs/payload/RequestFieldsSnippet.java index 2fb10150..85c2c118 100644 --- a/spring-restdocs-core/src/main/java/org/springframework/restdocs/payload/RequestFieldsSnippet.java +++ b/spring-restdocs-core/src/main/java/org/springframework/restdocs/payload/RequestFieldsSnippet.java @@ -22,6 +22,8 @@ import java.util.List; import java.util.Map; +import org.jspecify.annotations.Nullable; + import org.springframework.http.MediaType; import org.springframework.restdocs.operation.Operation; import org.springframework.restdocs.snippet.Snippet; @@ -64,7 +66,7 @@ protected RequestFieldsSnippet(List descriptors, boolean ignore * @param descriptors the descriptors * @param attributes the additional attributes */ - protected RequestFieldsSnippet(List descriptors, Map attributes) { + protected RequestFieldsSnippet(List descriptors, @Nullable Map attributes) { this(descriptors, attributes, false); } @@ -78,7 +80,7 @@ protected RequestFieldsSnippet(List descriptors, Map descriptors, Map attributes, + protected RequestFieldsSnippet(List descriptors, @Nullable Map attributes, boolean ignoreUndocumentedFields) { this(null, descriptors, attributes, ignoreUndocumentedFields); } @@ -91,7 +93,7 @@ protected RequestFieldsSnippet(List descriptors, Map subsectionExtractor, + protected RequestFieldsSnippet(@Nullable PayloadSubsectionExtractor subsectionExtractor, List descriptors) { this(subsectionExtractor, descriptors, null, false); } @@ -99,53 +101,59 @@ protected RequestFieldsSnippet(PayloadSubsectionExtractor subsectionExtractor /** * Creates a new {@code RequestFieldsSnippet} that will document the fields in the * subsection of the request extracted by the given {@code subsectionExtractor} using - * the given {@code descriptors}. If {@code ignoreUndocumentedFields} is {@code true}, - * undocumented fields will be ignored and will not trigger a failure. - * @param subsectionExtractor the subsection extractor document + * the given {@code descriptors}. If the extractor is {@code null}, the fields of the + * entire request will be documented. If {@code ignoreUndocumentedFields} is + * {@code true}, undocumented fields will be ignored and will not trigger a failure. + * @param subsectionExtractor the subsection extractor or {@code null} to document the + * fields of the entire request * @param descriptors the descriptors * @param ignoreUndocumentedFields whether undocumented fields should be ignored * @since 1.2.0 */ - protected RequestFieldsSnippet(PayloadSubsectionExtractor subsectionExtractor, List descriptors, - boolean ignoreUndocumentedFields) { + protected RequestFieldsSnippet(@Nullable PayloadSubsectionExtractor subsectionExtractor, + List descriptors, boolean ignoreUndocumentedFields) { this(subsectionExtractor, descriptors, null, ignoreUndocumentedFields); } /** * Creates a new {@code RequestFieldsSnippet} that will document the fields in the * subsection of the request extracted by the given {@code subsectionExtractor} using - * the given {@code descriptors}. The given {@code attributes} will be included in the - * model during template rendering. Undocumented fields will trigger a failure. - * @param subsectionExtractor the subsection extractor + * the given {@code descriptors}. If the extractor is {@code null}, the fields of the + * entire request will be documented. The given {@code attributes} will be included in + * the model during template rendering. Undocumented fields will trigger a failure. + * @param subsectionExtractor the subsection extractor or {@code null} to document the + * fields of the entire request * @param descriptors the descriptors * @param attributes the additional attributes * @since 1.2.0 */ - protected RequestFieldsSnippet(PayloadSubsectionExtractor subsectionExtractor, List descriptors, - Map attributes) { + protected RequestFieldsSnippet(@Nullable PayloadSubsectionExtractor subsectionExtractor, + List descriptors, @Nullable Map attributes) { this(subsectionExtractor, descriptors, attributes, false); } /** * Creates a new {@code RequestFieldsSnippet} that will document the fields in the * subsection of the request extracted by the given {@code subsectionExtractor} using - * the given {@code descriptors}. The given {@code attributes} will be included in the - * model during template rendering. If {@code ignoreUndocumentedFields} is + * the given {@code descriptors}. If the extractor is {@code null}, the fields of the + * entire request will be documented. The given {@code attributes} will be included in + * the model during template rendering. If {@code ignoreUndocumentedFields} is * {@code true}, undocumented fields will be ignored and will not trigger a failure. - * @param subsectionExtractor the path identifying the subsection of the payload to - * document + * @param subsectionExtractor the subsection extractor or {@code null} to document the + * fields of the entire request * @param descriptors the descriptors * @param attributes the additional attributes * @param ignoreUndocumentedFields whether undocumented fields should be ignored * @since 1.2.0 */ - protected RequestFieldsSnippet(PayloadSubsectionExtractor subsectionExtractor, List descriptors, - Map attributes, boolean ignoreUndocumentedFields) { + protected RequestFieldsSnippet(@Nullable PayloadSubsectionExtractor subsectionExtractor, + List descriptors, @Nullable Map attributes, + boolean ignoreUndocumentedFields) { super("request", descriptors, attributes, ignoreUndocumentedFields, subsectionExtractor); } @Override - protected MediaType getContentType(Operation operation) { + protected @Nullable MediaType getContentType(Operation operation) { return operation.getRequest().getHeaders().getContentType(); } diff --git a/spring-restdocs-core/src/main/java/org/springframework/restdocs/payload/RequestPartBodySnippet.java b/spring-restdocs-core/src/main/java/org/springframework/restdocs/payload/RequestPartBodySnippet.java index 69fb7cac..a1a7b6cf 100644 --- a/spring-restdocs-core/src/main/java/org/springframework/restdocs/payload/RequestPartBodySnippet.java +++ b/spring-restdocs-core/src/main/java/org/springframework/restdocs/payload/RequestPartBodySnippet.java @@ -19,6 +19,8 @@ import java.io.IOException; import java.util.Map; +import org.jspecify.annotations.Nullable; + import org.springframework.http.MediaType; import org.springframework.restdocs.operation.Operation; import org.springframework.restdocs.operation.OperationRequestPart; @@ -46,37 +48,41 @@ public RequestPartBodySnippet(String partName) { /** * Creates a new {@code RequestPartBodySnippet} that will document the subsection of * the body of the request part with the given {@code partName} extracted by the given - * {@code subsectionExtractor}. + * {@code subsectionExtractor}. If the extractor is {@code null} the entire body of + * the request part will be documented. * @param partName the name of the request part - * @param subsectionExtractor the subsection extractor + * @param subsectionExtractor the subsection extractor or {@code null} to document the + * request part's entire body */ - public RequestPartBodySnippet(String partName, PayloadSubsectionExtractor subsectionExtractor) { + public RequestPartBodySnippet(String partName, @Nullable PayloadSubsectionExtractor subsectionExtractor) { this(partName, subsectionExtractor, null); } /** * Creates a new {@code RequestPartBodySnippet} that will document the body of the - * request part with the given {@code partName}. The given additional + * request part with the given {@code partName}. If the extractor is {@code null} the + * entire body of the request part will be documented. The given additional * {@code attributes} will be included in the model during template rendering. * @param partName the name of the request part * @param attributes the additional attributes */ - public RequestPartBodySnippet(String partName, Map attributes) { + public RequestPartBodySnippet(String partName, @Nullable Map attributes) { this(partName, null, attributes); } /** - * Creates a new {@code RequestPartBodySnippet} that will document the body of the - * request part with the given {@code partName}. The subsection of the body extracted - * by the given {@code subsectionExtractor} will be documented and the given - * additional {@code attributes} that will be included in the model during template - * rendering. + * Creates a new {@code RequestPartBodySnippet} that will document the subsection of + * the body of the request part with the given {@code partName} extracted by the given + * {@code subsectionExtractor}. If the extractor is {@code null} the entire body of + * the request part will be documented. The given additional {@code attributes} will + * be included in the model during template rendering. * @param partName the name of the request part - * @param subsectionExtractor the subsection extractor + * @param subsectionExtractor the subsection extractor or {@code null} to document the + * request part's entire body * @param attributes the additional attributes */ - public RequestPartBodySnippet(String partName, PayloadSubsectionExtractor subsectionExtractor, - Map attributes) { + public RequestPartBodySnippet(String partName, @Nullable PayloadSubsectionExtractor subsectionExtractor, + @Nullable Map attributes) { super("request-part-" + partName, "request-part", subsectionExtractor, attributes); this.partName = partName; } @@ -87,7 +93,7 @@ protected byte[] getContent(Operation operation) throws IOException { } @Override - protected MediaType getContentType(Operation operation) { + protected @Nullable MediaType getContentType(Operation operation) { return findPart(operation).getHeaders().getContentType(); } diff --git a/spring-restdocs-core/src/main/java/org/springframework/restdocs/payload/RequestPartFieldsSnippet.java b/spring-restdocs-core/src/main/java/org/springframework/restdocs/payload/RequestPartFieldsSnippet.java index cd7cac59..113dc414 100644 --- a/spring-restdocs-core/src/main/java/org/springframework/restdocs/payload/RequestPartFieldsSnippet.java +++ b/spring-restdocs-core/src/main/java/org/springframework/restdocs/payload/RequestPartFieldsSnippet.java @@ -22,6 +22,8 @@ import java.util.List; import java.util.Map; +import org.jspecify.annotations.Nullable; + import org.springframework.http.MediaType; import org.springframework.restdocs.operation.Operation; import org.springframework.restdocs.operation.OperationRequestPart; @@ -76,7 +78,7 @@ protected RequestPartFieldsSnippet(String partName, List descri * @param attributes the additional attributes */ protected RequestPartFieldsSnippet(String partName, List descriptors, - Map attributes) { + @Nullable Map attributes) { this(partName, descriptors, attributes, false); } @@ -92,20 +94,22 @@ protected RequestPartFieldsSnippet(String partName, List descri * @param ignoreUndocumentedFields whether undocumented fields should be ignored */ protected RequestPartFieldsSnippet(String partName, List descriptors, - Map attributes, boolean ignoreUndocumentedFields) { + @Nullable Map attributes, boolean ignoreUndocumentedFields) { this(partName, null, descriptors, attributes, ignoreUndocumentedFields); } /** * Creates a new {@code RequestPartFieldsSnippet} that will document the fields in a * subsection of the request part using the given {@code descriptors}. The subsection - * will be extracted using the given {@code subsectionExtractor}. Undocumented fields - * will trigger a failure. + * will be extracted using the given {@code subsectionExtractor}. If the extractor is + * {@code null} the fields of the entire request part will be documented. Undocumented + * fields will trigger a failure. * @param partName the part name - * @param subsectionExtractor the subsection extractor + * @param subsectionExtractor the subsection extractor, or {@code null} to document + * the fields of the entire request part * @param descriptors the descriptors */ - protected RequestPartFieldsSnippet(String partName, PayloadSubsectionExtractor subsectionExtractor, + protected RequestPartFieldsSnippet(String partName, @Nullable PayloadSubsectionExtractor subsectionExtractor, List descriptors) { this(partName, subsectionExtractor, descriptors, null, false); } @@ -113,15 +117,17 @@ protected RequestPartFieldsSnippet(String partName, PayloadSubsectionExtractor subsectionExtractor, + protected RequestPartFieldsSnippet(String partName, @Nullable PayloadSubsectionExtractor subsectionExtractor, List descriptors, boolean ignoreUndocumentedFields) { this(partName, subsectionExtractor, descriptors, null, ignoreUndocumentedFields); } @@ -129,41 +135,46 @@ protected RequestPartFieldsSnippet(String partName, PayloadSubsectionExtractor subsectionExtractor, - List descriptors, Map attributes) { + protected RequestPartFieldsSnippet(String partName, @Nullable PayloadSubsectionExtractor subsectionExtractor, + List descriptors, @Nullable Map attributes) { this(partName, subsectionExtractor, descriptors, attributes, false); } /** * Creates a new {@code RequestPartFieldsSnippet} that will document the fields in a * subsection of the request part using the given {@code descriptors}. The subsection - * will be extracted using the given {@code subsectionExtractor}. The given + * will be extracted using the given {@code subsectionExtractor}. If the extractor is + * {@code null} the fields of the entire request part will be documented. The given * {@code attributes} will be included in the model during template rendering. If * {@code ignoreUndocumentedFields} is {@code true}, undocumented fields will be * ignored and will not trigger a failure. * @param partName the part name - * @param subsectionExtractor the subsection extractor + * @param subsectionExtractor the subsection extractor, or {@code null} to document + * the fields of the entire request part * @param descriptors the descriptors * @param attributes the additional attributes * @param ignoreUndocumentedFields whether undocumented fields should be ignored */ - protected RequestPartFieldsSnippet(String partName, PayloadSubsectionExtractor subsectionExtractor, - List descriptors, Map attributes, boolean ignoreUndocumentedFields) { + protected RequestPartFieldsSnippet(String partName, @Nullable PayloadSubsectionExtractor subsectionExtractor, + List descriptors, @Nullable Map attributes, + boolean ignoreUndocumentedFields) { super("request-part-" + partName, "request-part", descriptors, attributes, ignoreUndocumentedFields, subsectionExtractor); this.partName = partName; } @Override - protected MediaType getContentType(Operation operation) { + protected @Nullable MediaType getContentType(Operation operation) { return findPart(operation).getHeaders().getContentType(); } diff --git a/spring-restdocs-core/src/main/java/org/springframework/restdocs/payload/ResponseBodySnippet.java b/spring-restdocs-core/src/main/java/org/springframework/restdocs/payload/ResponseBodySnippet.java index a081cc84..8c31db1d 100644 --- a/spring-restdocs-core/src/main/java/org/springframework/restdocs/payload/ResponseBodySnippet.java +++ b/spring-restdocs-core/src/main/java/org/springframework/restdocs/payload/ResponseBodySnippet.java @@ -19,6 +19,8 @@ import java.io.IOException; import java.util.Map; +import org.jspecify.annotations.Nullable; + import org.springframework.http.MediaType; import org.springframework.restdocs.operation.Operation; import org.springframework.restdocs.snippet.Snippet; @@ -39,10 +41,12 @@ public ResponseBodySnippet() { /** * Creates a new {@code ResponseBodySnippet} that will document the subsection of the - * response body extracted by the given {@code subsectionExtractor}. - * @param subsectionExtractor the subsection extractor + * response body extracted by the given {@code subsectionExtractor}. If the extractor + * is {@code null} the entire response body will be documented. + * @param subsectionExtractor the subsection extractor, or {@code null} to document + * the entire response body */ - public ResponseBodySnippet(PayloadSubsectionExtractor subsectionExtractor) { + public ResponseBodySnippet(@Nullable PayloadSubsectionExtractor subsectionExtractor) { this(subsectionExtractor, null); } @@ -51,19 +55,21 @@ public ResponseBodySnippet(PayloadSubsectionExtractor subsectionExtractor) { * {@code attributes} that will be included in the model during template rendering. * @param attributes the additional attributes */ - public ResponseBodySnippet(Map attributes) { + public ResponseBodySnippet(@Nullable Map attributes) { this(null, attributes); } /** * Creates a new {@code ResponseBodySnippet} that will document the subsection of the - * response body extracted by the given {@code subsectionExtractor}. The given - * additional {@code attributes} that will be included in the model during template - * rendering. - * @param subsectionExtractor the subsection extractor + * response body extracted by the given {@code subsectionExtractor}. If the extractor + * is {@code null} the entire response body will be documented. The given additional + * {@code attributes} that will be included in the model during template rendering. + * @param subsectionExtractor the subsection extractor, or {@code null} to document + * the entire response body * @param attributes the additional attributes */ - public ResponseBodySnippet(PayloadSubsectionExtractor subsectionExtractor, Map attributes) { + public ResponseBodySnippet(@Nullable PayloadSubsectionExtractor subsectionExtractor, + @Nullable Map attributes) { super("response", subsectionExtractor, attributes); } @@ -73,7 +79,7 @@ protected byte[] getContent(Operation operation) throws IOException { } @Override - protected MediaType getContentType(Operation operation) { + protected @Nullable MediaType getContentType(Operation operation) { return operation.getResponse().getHeaders().getContentType(); } diff --git a/spring-restdocs-core/src/main/java/org/springframework/restdocs/payload/ResponseFieldsSnippet.java b/spring-restdocs-core/src/main/java/org/springframework/restdocs/payload/ResponseFieldsSnippet.java index b15d7785..02a80345 100644 --- a/spring-restdocs-core/src/main/java/org/springframework/restdocs/payload/ResponseFieldsSnippet.java +++ b/spring-restdocs-core/src/main/java/org/springframework/restdocs/payload/ResponseFieldsSnippet.java @@ -22,6 +22,8 @@ import java.util.List; import java.util.Map; +import org.jspecify.annotations.Nullable; + import org.springframework.http.MediaType; import org.springframework.restdocs.operation.Operation; import org.springframework.restdocs.snippet.Snippet; @@ -65,7 +67,7 @@ protected ResponseFieldsSnippet(List descriptors, boolean ignor * @param descriptors the descriptors * @param attributes the additional attributes */ - protected ResponseFieldsSnippet(List descriptors, Map attributes) { + protected ResponseFieldsSnippet(List descriptors, @Nullable Map attributes) { this(descriptors, attributes, false); } @@ -79,7 +81,7 @@ protected ResponseFieldsSnippet(List descriptors, Map descriptors, Map attributes, + protected ResponseFieldsSnippet(List descriptors, @Nullable Map attributes, boolean ignoreUndocumentedFields) { this(null, descriptors, attributes, ignoreUndocumentedFields); } @@ -87,9 +89,11 @@ protected ResponseFieldsSnippet(List descriptors, Map subsectionExtracto /** * Creates a new {@code ResponseFieldsSnippet} that will document the fields in the * subsection of the response using the given {@code descriptors}. The subsection will - * be extracted using the given {@code subsectionExtractor}. If + * be extracted using the given {@code subsectionExtractor}. If the extractor is + * {@code null} the fields of the entire response will be documented. If * {@code ignoreUndocumentedFields} is {@code true}, undocumented fields will be * ignored and will not trigger a failure. - * @param subsectionExtractor the subsection extractor + * @param subsectionExtractor the subsection extractor, or {@code null} to document + * the fields of the entire response * @param descriptors the descriptors * @param ignoreUndocumentedFields whether undocumented fields should be ignored * @since 1.2.0 @@ -117,10 +123,12 @@ protected ResponseFieldsSnippet(PayloadSubsectionExtractor subsectionExtracto /** * Creates a new {@code ResponseFieldsSnippet} that will document the fields in a * subsection of the response using the given {@code descriptors}. The subsection will - * be extracted using the given {@code subsectionExtractor}. The given + * be extracted using the given {@code subsectionExtractor}. If the extractor is + * {@code null} the fields of the entire response will be documented. The given * {@code attributes} will be included in the model during template rendering. * Undocumented fields will trigger a failure. - * @param subsectionExtractor the subsection extractor + * @param subsectionExtractor the subsection extractor, or {@code null} to document + * the fields of the entire response * @param descriptors the descriptors * @param attributes the additional attributes * @since 1.2.0 @@ -133,23 +141,26 @@ protected ResponseFieldsSnippet(PayloadSubsectionExtractor subsectionExtracto /** * Creates a new {@code ResponseFieldsSnippet} that will document the fields in a * subsection of the response using the given {@code descriptors}. The subsection will - * be extracted using the given {@code subsectionExtractor}. The given + * be extracted using the given {@code subsectionExtractor}. If the extractor is + * {@code null} the fields of the entire response will be documented. The given * {@code attributes} will be included in the model during template rendering. If * {@code ignoreUndocumentedFields} is {@code true}, undocumented fields will be * ignored and will not trigger a failure. - * @param subsectionExtractor the subsection extractor + * @param subsectionExtractor the subsection extractor, or {@code null} to document + * the fields of the entire response * @param descriptors the descriptors * @param attributes the additional attributes * @param ignoreUndocumentedFields whether undocumented fields should be ignored * @since 1.2.0 */ - protected ResponseFieldsSnippet(PayloadSubsectionExtractor subsectionExtractor, - List descriptors, Map attributes, boolean ignoreUndocumentedFields) { + protected ResponseFieldsSnippet(@Nullable PayloadSubsectionExtractor subsectionExtractor, + List descriptors, @Nullable Map attributes, + boolean ignoreUndocumentedFields) { super("response", descriptors, attributes, ignoreUndocumentedFields, subsectionExtractor); } @Override - protected MediaType getContentType(Operation operation) { + protected @Nullable MediaType getContentType(Operation operation) { return operation.getResponse().getHeaders().getContentType(); } diff --git a/spring-restdocs-core/src/main/java/org/springframework/restdocs/payload/XmlContentHandler.java b/spring-restdocs-core/src/main/java/org/springframework/restdocs/payload/XmlContentHandler.java index 9316d053..62c510d8 100644 --- a/spring-restdocs-core/src/main/java/org/springframework/restdocs/payload/XmlContentHandler.java +++ b/spring-restdocs-core/src/main/java/org/springframework/restdocs/payload/XmlContentHandler.java @@ -35,6 +35,7 @@ import javax.xml.xpath.XPathExpressionException; import javax.xml.xpath.XPathFactory; +import org.jspecify.annotations.Nullable; import org.w3c.dom.Attr; import org.w3c.dom.Document; import org.w3c.dom.Element; @@ -106,7 +107,7 @@ private XPathExpression createXPath(String fieldPath) throws XPathExpressionExce } @Override - public String getUndocumentedContent() { + public @Nullable String getUndocumentedContent() { Document payload = readPayload(); List matchedButNotRemoved = new ArrayList<>(); for (FieldDescriptor fieldDescriptor : this.fieldDescriptors) { diff --git a/spring-restdocs-core/src/main/java/org/springframework/restdocs/payload/package-info.java b/spring-restdocs-core/src/main/java/org/springframework/restdocs/payload/package-info.java index 15493dcf..77dc1269 100644 --- a/spring-restdocs-core/src/main/java/org/springframework/restdocs/payload/package-info.java +++ b/spring-restdocs-core/src/main/java/org/springframework/restdocs/payload/package-info.java @@ -17,4 +17,7 @@ /** * Documenting the payload of a RESTful API's requests and responses. */ +@NullMarked package org.springframework.restdocs.payload; + +import org.jspecify.annotations.NullMarked; diff --git a/spring-restdocs-core/src/main/java/org/springframework/restdocs/request/AbstractParametersSnippet.java b/spring-restdocs-core/src/main/java/org/springframework/restdocs/request/AbstractParametersSnippet.java index 3888db2c..ba6dde32 100644 --- a/spring-restdocs-core/src/main/java/org/springframework/restdocs/request/AbstractParametersSnippet.java +++ b/spring-restdocs-core/src/main/java/org/springframework/restdocs/request/AbstractParametersSnippet.java @@ -26,6 +26,8 @@ import java.util.Map.Entry; import java.util.Set; +import org.jspecify.annotations.Nullable; + import org.springframework.restdocs.operation.Operation; import org.springframework.restdocs.snippet.TemplatedSnippet; import org.springframework.util.Assert; @@ -56,7 +58,7 @@ public abstract class AbstractParametersSnippet extends TemplatedSnippet { * ignored */ protected AbstractParametersSnippet(String snippetName, List descriptors, - Map attributes, boolean ignoreUndocumentedParameters) { + @Nullable Map attributes, boolean ignoreUndocumentedParameters) { super(snippetName, attributes); for (ParameterDescriptor descriptor : descriptors) { Assert.notNull(descriptor.getName(), "Parameter descriptors must have a name"); diff --git a/spring-restdocs-core/src/main/java/org/springframework/restdocs/request/FormParametersSnippet.java b/spring-restdocs-core/src/main/java/org/springframework/restdocs/request/FormParametersSnippet.java index aa53b47d..577ddb37 100644 --- a/spring-restdocs-core/src/main/java/org/springframework/restdocs/request/FormParametersSnippet.java +++ b/spring-restdocs-core/src/main/java/org/springframework/restdocs/request/FormParametersSnippet.java @@ -22,6 +22,8 @@ import java.util.Map; import java.util.Set; +import org.jspecify.annotations.Nullable; + import org.springframework.restdocs.operation.FormParameters; import org.springframework.restdocs.operation.Operation; import org.springframework.restdocs.snippet.Snippet; @@ -68,7 +70,7 @@ protected FormParametersSnippet(List descriptors, boolean i * @param descriptors the parameter descriptors * @param attributes the additional attributes */ - protected FormParametersSnippet(List descriptors, Map attributes) { + protected FormParametersSnippet(List descriptors, @Nullable Map attributes) { this(descriptors, attributes, false); } @@ -83,7 +85,7 @@ protected FormParametersSnippet(List descriptors, Map descriptors, Map attributes, + protected FormParametersSnippet(List descriptors, @Nullable Map attributes, boolean ignoreUndocumentedParameters) { super("form-parameters", descriptors, attributes, ignoreUndocumentedParameters); } diff --git a/spring-restdocs-core/src/main/java/org/springframework/restdocs/request/PathParametersSnippet.java b/spring-restdocs-core/src/main/java/org/springframework/restdocs/request/PathParametersSnippet.java index d49ec129..fa41b84c 100644 --- a/spring-restdocs-core/src/main/java/org/springframework/restdocs/request/PathParametersSnippet.java +++ b/spring-restdocs-core/src/main/java/org/springframework/restdocs/request/PathParametersSnippet.java @@ -25,6 +25,8 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; +import org.jspecify.annotations.Nullable; + import org.springframework.restdocs.generate.RestDocumentationGenerator; import org.springframework.restdocs.operation.Operation; import org.springframework.restdocs.snippet.Snippet; @@ -73,7 +75,7 @@ protected PathParametersSnippet(List descriptors, boolean i * @param descriptors the parameter descriptors * @param attributes the additional attributes */ - protected PathParametersSnippet(List descriptors, Map attributes) { + protected PathParametersSnippet(List descriptors, @Nullable Map attributes) { this(descriptors, attributes, false); } @@ -88,7 +90,7 @@ protected PathParametersSnippet(List descriptors, Map descriptors, Map attributes, + protected PathParametersSnippet(List descriptors, @Nullable Map attributes, boolean ignoreUndocumentedParameters) { super("path-parameters", descriptors, attributes, ignoreUndocumentedParameters); } diff --git a/spring-restdocs-core/src/main/java/org/springframework/restdocs/request/QueryParametersSnippet.java b/spring-restdocs-core/src/main/java/org/springframework/restdocs/request/QueryParametersSnippet.java index 51e2144d..1273d72b 100644 --- a/spring-restdocs-core/src/main/java/org/springframework/restdocs/request/QueryParametersSnippet.java +++ b/spring-restdocs-core/src/main/java/org/springframework/restdocs/request/QueryParametersSnippet.java @@ -22,6 +22,8 @@ import java.util.Map; import java.util.Set; +import org.jspecify.annotations.Nullable; + import org.springframework.restdocs.operation.Operation; import org.springframework.restdocs.operation.QueryParameters; import org.springframework.restdocs.snippet.Snippet; @@ -68,7 +70,7 @@ protected QueryParametersSnippet(List descriptors, boolean * @param descriptors the parameter descriptors * @param attributes the additional attributes */ - protected QueryParametersSnippet(List descriptors, Map attributes) { + protected QueryParametersSnippet(List descriptors, @Nullable Map attributes) { this(descriptors, attributes, false); } @@ -83,7 +85,7 @@ protected QueryParametersSnippet(List descriptors, Map descriptors, Map attributes, + protected QueryParametersSnippet(List descriptors, @Nullable Map attributes, boolean ignoreUndocumentedParameters) { super("query-parameters", descriptors, attributes, ignoreUndocumentedParameters); } diff --git a/spring-restdocs-core/src/main/java/org/springframework/restdocs/request/RequestPartsSnippet.java b/spring-restdocs-core/src/main/java/org/springframework/restdocs/request/RequestPartsSnippet.java index a3f8d775..ded1ac1c 100644 --- a/spring-restdocs-core/src/main/java/org/springframework/restdocs/request/RequestPartsSnippet.java +++ b/spring-restdocs-core/src/main/java/org/springframework/restdocs/request/RequestPartsSnippet.java @@ -27,6 +27,8 @@ import java.util.Map.Entry; import java.util.Set; +import org.jspecify.annotations.Nullable; + import org.springframework.restdocs.operation.Operation; import org.springframework.restdocs.operation.OperationRequestPart; import org.springframework.restdocs.snippet.Snippet; @@ -77,7 +79,7 @@ protected RequestPartsSnippet(List descriptors, boolean i * @param descriptors the parameter descriptors * @param attributes the additional attributes */ - protected RequestPartsSnippet(List descriptors, Map attributes) { + protected RequestPartsSnippet(List descriptors, @Nullable Map attributes) { this(descriptors, attributes, false); } @@ -90,7 +92,7 @@ protected RequestPartsSnippet(List descriptors, Map descriptors, Map attributes, + protected RequestPartsSnippet(List descriptors, @Nullable Map attributes, boolean ignoreUndocumentedParts) { super("request-parts", attributes); for (RequestPartDescriptor descriptor : descriptors) { diff --git a/spring-restdocs-core/src/main/java/org/springframework/restdocs/request/package-info.java b/spring-restdocs-core/src/main/java/org/springframework/restdocs/request/package-info.java index 70b1d5a1..ef05ed6d 100644 --- a/spring-restdocs-core/src/main/java/org/springframework/restdocs/request/package-info.java +++ b/spring-restdocs-core/src/main/java/org/springframework/restdocs/request/package-info.java @@ -17,4 +17,7 @@ /** * Documenting query and path parameters of requests sent to a RESTful API. */ +@NullMarked package org.springframework.restdocs.request; + +import org.jspecify.annotations.NullMarked; diff --git a/spring-restdocs-core/src/main/java/org/springframework/restdocs/snippet/AbstractDescriptor.java b/spring-restdocs-core/src/main/java/org/springframework/restdocs/snippet/AbstractDescriptor.java index bb1e927f..d1db2ec5 100644 --- a/spring-restdocs-core/src/main/java/org/springframework/restdocs/snippet/AbstractDescriptor.java +++ b/spring-restdocs-core/src/main/java/org/springframework/restdocs/snippet/AbstractDescriptor.java @@ -19,6 +19,8 @@ import java.util.HashMap; import java.util.Map; +import org.jspecify.annotations.Nullable; + import org.springframework.restdocs.snippet.Attributes.Attribute; /** @@ -32,7 +34,7 @@ public abstract class AbstractDescriptor> { private Map attributes = new HashMap<>(); - private Object description; + private @Nullable Object description; /** * Adds the given {@code attributes} to the descriptor. @@ -53,7 +55,7 @@ public final T attributes(Attribute... attributes) { * @return the descriptor */ @SuppressWarnings("unchecked") - public final T description(Object description) { + public final T description(@Nullable Object description) { this.description = description; return (T) this; } @@ -62,7 +64,7 @@ public final T description(Object description) { * Returns the description. * @return the description */ - public final Object getDescription() { + public final @Nullable Object getDescription() { return this.description; } diff --git a/spring-restdocs-core/src/main/java/org/springframework/restdocs/snippet/RestDocumentationContextPlaceholderResolver.java b/spring-restdocs-core/src/main/java/org/springframework/restdocs/snippet/RestDocumentationContextPlaceholderResolver.java index 442f53e6..1c5c20dd 100644 --- a/spring-restdocs-core/src/main/java/org/springframework/restdocs/snippet/RestDocumentationContextPlaceholderResolver.java +++ b/spring-restdocs-core/src/main/java/org/springframework/restdocs/snippet/RestDocumentationContextPlaceholderResolver.java @@ -16,6 +16,8 @@ package org.springframework.restdocs.snippet; +import org.jspecify.annotations.Nullable; + import org.springframework.restdocs.RestDocumentationContext; import org.springframework.util.PropertyPlaceholderHelper.PlaceholderResolver; @@ -60,7 +62,7 @@ public RestDocumentationContextPlaceholderResolver(RestDocumentationContext cont } @Override - public String resolvePlaceholder(String placeholderName) { + public @Nullable String resolvePlaceholder(String placeholderName) { if ("step".equals(placeholderName)) { return Integer.toString(this.context.getStepCount()); } @@ -71,7 +73,7 @@ public String resolvePlaceholder(String placeholderName) { return tryClassNameConversion(placeholderName); } - private String tryMethodNameConversion(String placeholderName) { + private @Nullable String tryMethodNameConversion(String placeholderName) { if ("methodName".equals(placeholderName)) { return this.context.getTestMethodName(); } @@ -84,7 +86,7 @@ private String tryMethodNameConversion(String placeholderName) { return null; } - private String tryClassNameConversion(String placeholderName) { + private @Nullable String tryClassNameConversion(String placeholderName) { if ("ClassName".equals(placeholderName)) { return this.context.getTestClass().getSimpleName(); } diff --git a/spring-restdocs-core/src/main/java/org/springframework/restdocs/snippet/StandardWriterResolver.java b/spring-restdocs-core/src/main/java/org/springframework/restdocs/snippet/StandardWriterResolver.java index ce96f4fc..4b26e225 100644 --- a/spring-restdocs-core/src/main/java/org/springframework/restdocs/snippet/StandardWriterResolver.java +++ b/spring-restdocs-core/src/main/java/org/springframework/restdocs/snippet/StandardWriterResolver.java @@ -22,6 +22,8 @@ import java.io.OutputStreamWriter; import java.io.Writer; +import org.jspecify.annotations.Nullable; + import org.springframework.restdocs.RestDocumentationContext; import org.springframework.restdocs.templates.TemplateFormat; import org.springframework.util.PropertyPlaceholderHelper; @@ -81,7 +83,7 @@ private String replacePlaceholders(PlaceholderResolver resolver, String input) { return this.propertyPlaceholderHelper.replacePlaceholders(input, resolver); } - File resolveFile(String outputDirectory, String fileName, RestDocumentationContext context) { + @Nullable File resolveFile(String outputDirectory, String fileName, RestDocumentationContext context) { File outputFile = new File(outputDirectory, fileName); if (!outputFile.isAbsolute()) { outputFile = makeRelativeToConfiguredOutputDir(outputFile, context); @@ -89,7 +91,7 @@ File resolveFile(String outputDirectory, String fileName, RestDocumentationConte return outputFile; } - private File makeRelativeToConfiguredOutputDir(File outputFile, RestDocumentationContext context) { + private @Nullable File makeRelativeToConfiguredOutputDir(File outputFile, RestDocumentationContext context) { File configuredOutputDir = context.getOutputDirectory(); if (configuredOutputDir != null) { return new File(configuredOutputDir, outputFile.getPath()); diff --git a/spring-restdocs-core/src/main/java/org/springframework/restdocs/snippet/TemplatedSnippet.java b/spring-restdocs-core/src/main/java/org/springframework/restdocs/snippet/TemplatedSnippet.java index 5612c688..3857492f 100644 --- a/spring-restdocs-core/src/main/java/org/springframework/restdocs/snippet/TemplatedSnippet.java +++ b/spring-restdocs-core/src/main/java/org/springframework/restdocs/snippet/TemplatedSnippet.java @@ -21,6 +21,8 @@ import java.util.HashMap; import java.util.Map; +import org.jspecify.annotations.Nullable; + import org.springframework.restdocs.RestDocumentationContext; import org.springframework.restdocs.operation.Operation; import org.springframework.restdocs.templates.Template; @@ -49,7 +51,7 @@ public abstract class TemplatedSnippet implements Snippet { * @param attributes the additional attributes * @see #TemplatedSnippet(String, String, Map) */ - protected TemplatedSnippet(String snippetName, Map attributes) { + protected TemplatedSnippet(String snippetName, @Nullable Map attributes) { this(snippetName, snippetName, attributes); } @@ -61,7 +63,7 @@ protected TemplatedSnippet(String snippetName, Map attributes) { * @param templateName the name of the template * @param attributes the additional attributes */ - protected TemplatedSnippet(String snippetName, String templateName, Map attributes) { + protected TemplatedSnippet(String snippetName, String templateName, @Nullable Map attributes) { this.templateName = templateName; this.snippetName = snippetName; if (attributes != null) { @@ -71,18 +73,25 @@ protected TemplatedSnippet(String snippetName, String templateName, Map model = createModel(operation); + model.putAll(this.attributes); try (Writer writer = writerResolver.resolve(operation.getName(), this.snippetName, context)) { - Map model = createModel(operation); - model.putAll(this.attributes); - TemplateEngine templateEngine = (TemplateEngine) operation.getAttributes() - .get(TemplateEngine.class.getName()); + TemplateEngine templateEngine = getRequiredAttribute(operation, TemplateEngine.class); writer.append(templateEngine.compileTemplate(this.templateName).render(model)); } } + @SuppressWarnings("unchecked") + private T getRequiredAttribute(Operation operation, Class type) { + T attribute = (T) operation.getAttributes().get(type.getName()); + if (attribute == null) { + throw new SnippetException("Operation must have a non-null " + type.getName() + " attribute"); + } + return attribute; + } + /** * Create the model that should be used during template rendering to document the * given {@code operation}. Any additional attributes that were supplied when this diff --git a/spring-restdocs-core/src/main/java/org/springframework/restdocs/snippet/package-info.java b/spring-restdocs-core/src/main/java/org/springframework/restdocs/snippet/package-info.java index 2dc47a69..521a2586 100644 --- a/spring-restdocs-core/src/main/java/org/springframework/restdocs/snippet/package-info.java +++ b/spring-restdocs-core/src/main/java/org/springframework/restdocs/snippet/package-info.java @@ -17,4 +17,7 @@ /** * Snippet generation. */ +@NullMarked package org.springframework.restdocs.snippet; + +import org.jspecify.annotations.NullMarked; diff --git a/spring-restdocs-core/src/main/java/org/springframework/restdocs/templates/mustache/package-info.java b/spring-restdocs-core/src/main/java/org/springframework/restdocs/templates/mustache/package-info.java index a5b5c5c0..0a837111 100644 --- a/spring-restdocs-core/src/main/java/org/springframework/restdocs/templates/mustache/package-info.java +++ b/spring-restdocs-core/src/main/java/org/springframework/restdocs/templates/mustache/package-info.java @@ -17,4 +17,7 @@ /** * JMustache-based implementation of the template API. */ +@NullMarked package org.springframework.restdocs.templates.mustache; + +import org.jspecify.annotations.NullMarked; diff --git a/spring-restdocs-core/src/main/java/org/springframework/restdocs/templates/package-info.java b/spring-restdocs-core/src/main/java/org/springframework/restdocs/templates/package-info.java index 1ff012d5..deb980c1 100644 --- a/spring-restdocs-core/src/main/java/org/springframework/restdocs/templates/package-info.java +++ b/spring-restdocs-core/src/main/java/org/springframework/restdocs/templates/package-info.java @@ -17,4 +17,7 @@ /** * Template API used to render documentation snippets. */ +@NullMarked package org.springframework.restdocs.templates; + +import org.jspecify.annotations.NullMarked; diff --git a/spring-restdocs-core/src/main/resources/org/springframework/restdocs/constraints/DefaultConstraintDescriptions.properties b/spring-restdocs-core/src/main/resources/org/springframework/restdocs/constraints/DefaultConstraintDescriptions.properties index 248cb238..699b900e 100644 --- a/spring-restdocs-core/src/main/resources/org/springframework/restdocs/constraints/DefaultConstraintDescriptions.properties +++ b/spring-restdocs-core/src/main/resources/org/springframework/restdocs/constraints/DefaultConstraintDescriptions.properties @@ -24,12 +24,9 @@ org.hibernate.validator.constraints.CodePointLength.description=Code point lengt org.hibernate.validator.constraints.CreditCardNumber.description=Must be a well-formed credit card number org.hibernate.validator.constraints.Currency.description=Must be in an accepted currency unit (${value}) org.hibernate.validator.constraints.EAN.description=Must be a well-formed ${type} number -org.hibernate.validator.constraints.Email.description=Must be a well-formed email address org.hibernate.validator.constraints.Length.description=Length must be between ${min} and ${max} inclusive org.hibernate.validator.constraints.LuhnCheck.description=Must pass the Luhn Modulo 10 checksum algorithm org.hibernate.validator.constraints.Mod10Check.description=Must pass the Mod10 checksum algorithm org.hibernate.validator.constraints.Mod11Check.description=Must pass the Mod11 checksum algorithm -org.hibernate.validator.constraints.NotBlank.description=Must not be blank -org.hibernate.validator.constraints.NotEmpty.description=Must not be empty org.hibernate.validator.constraints.Range.description=Must be at least ${min} and at most ${max} org.hibernate.validator.constraints.URL.description=Must be a well-formed URL \ No newline at end of file diff --git a/spring-restdocs-core/src/test/java/org/springframework/restdocs/AbstractSnippetTests.java b/spring-restdocs-core/src/test/java/org/springframework/restdocs/AbstractSnippetTests.java index 2d159d2b..7afe1cbb 100644 --- a/spring-restdocs-core/src/test/java/org/springframework/restdocs/AbstractSnippetTests.java +++ b/spring-restdocs-core/src/test/java/org/springframework/restdocs/AbstractSnippetTests.java @@ -16,25 +16,16 @@ package org.springframework.restdocs; -import java.util.Arrays; -import java.util.List; - -import org.junit.Rule; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; -import org.junit.runners.Parameterized.Parameters; - import org.springframework.core.io.FileSystemResource; import org.springframework.http.HttpStatus; import org.springframework.restdocs.templates.TemplateFormat; import org.springframework.restdocs.templates.TemplateFormats; -import org.springframework.restdocs.testfixtures.GeneratedSnippets; -import org.springframework.restdocs.testfixtures.OperationBuilder; import org.springframework.restdocs.testfixtures.SnippetConditions; import org.springframework.restdocs.testfixtures.SnippetConditions.CodeBlockCondition; import org.springframework.restdocs.testfixtures.SnippetConditions.HttpRequestCondition; import org.springframework.restdocs.testfixtures.SnippetConditions.HttpResponseCondition; import org.springframework.restdocs.testfixtures.SnippetConditions.TableCondition; +import org.springframework.restdocs.testfixtures.jupiter.AssertableSnippets; import org.springframework.web.bind.annotation.RequestMethod; /** @@ -42,28 +33,11 @@ * * @author Andy Wilkinson */ -@RunWith(Parameterized.class) public abstract class AbstractSnippetTests { - protected final TemplateFormat templateFormat; - - @Rule - public GeneratedSnippets generatedSnippets; - - @Rule - public OperationBuilder operationBuilder; + protected final TemplateFormat templateFormat = TemplateFormats.asciidoctor(); - @Parameters(name = "{0}") - public static List parameters() { - return Arrays.asList(new Object[] { "Asciidoctor", TemplateFormats.asciidoctor() }, - new Object[] { "Markdown", TemplateFormats.markdown() }); - } - - protected AbstractSnippetTests(String name, TemplateFormat templateFormat) { - this.generatedSnippets = new GeneratedSnippets(templateFormat); - this.templateFormat = templateFormat; - this.operationBuilder = new OperationBuilder(this.templateFormat); - } + protected AssertableSnippets snippets; public CodeBlockCondition codeBlock(String language) { return this.codeBlock(language, null); diff --git a/spring-restdocs-core/src/test/java/org/springframework/restdocs/RestDocumentationGeneratorTests.java b/spring-restdocs-core/src/test/java/org/springframework/restdocs/RestDocumentationGeneratorTests.java index 7c5cd24f..71775082 100644 --- a/spring-restdocs-core/src/test/java/org/springframework/restdocs/RestDocumentationGeneratorTests.java +++ b/spring-restdocs-core/src/test/java/org/springframework/restdocs/RestDocumentationGeneratorTests.java @@ -22,7 +22,7 @@ import java.util.HashMap; import java.util.Map; -import org.junit.Test; +import org.junit.jupiter.api.Test; import org.mockito.ArgumentCaptor; import org.mockito.InOrder; import org.mockito.Mockito; @@ -55,7 +55,7 @@ * @author Andy Wilkinson * @author Filip Hrisafov */ -public class RestDocumentationGeneratorTests { +class RestDocumentationGeneratorTests { @SuppressWarnings("unchecked") private final RequestConverter requestConverter = mock(RequestConverter.class); @@ -80,7 +80,7 @@ public class RestDocumentationGeneratorTests { private final OperationPreprocessor responsePreprocessor = mock(OperationPreprocessor.class); @Test - public void basicHandling() throws IOException { + void basicHandling() throws IOException { given(this.requestConverter.convert(this.request)).willReturn(this.operationRequest); given(this.responseConverter.convert(this.response)).willReturn(this.operationResponse); HashMap configuration = new HashMap<>(); @@ -90,7 +90,7 @@ public void basicHandling() throws IOException { } @Test - public void defaultSnippetsAreCalled() throws IOException { + void defaultSnippetsAreCalled() throws IOException { given(this.requestConverter.convert(this.request)).willReturn(this.operationRequest); given(this.responseConverter.convert(this.response)).willReturn(this.operationResponse); HashMap configuration = new HashMap<>(); @@ -107,7 +107,7 @@ public void defaultSnippetsAreCalled() throws IOException { } @Test - public void defaultOperationRequestPreprocessorsAreCalled() throws IOException { + void defaultOperationRequestPreprocessorsAreCalled() throws IOException { given(this.requestConverter.convert(this.request)).willReturn(this.operationRequest); given(this.responseConverter.convert(this.response)).willReturn(this.operationResponse); HashMap configuration = new HashMap<>(); @@ -128,7 +128,7 @@ public void defaultOperationRequestPreprocessorsAreCalled() throws IOException { } @Test - public void defaultOperationResponsePreprocessorsAreCalled() throws IOException { + void defaultOperationResponsePreprocessorsAreCalled() throws IOException { given(this.requestConverter.convert(this.request)).willReturn(this.operationRequest); given(this.responseConverter.convert(this.response)).willReturn(this.operationResponse); HashMap configuration = new HashMap<>(); @@ -149,7 +149,7 @@ public void defaultOperationResponsePreprocessorsAreCalled() throws IOException } @Test - public void newGeneratorOnlyCallsItsSnippets() throws IOException { + void newGeneratorOnlyCallsItsSnippets() throws IOException { OperationRequestPreprocessor requestPreprocessor = mock(OperationRequestPreprocessor.class); OperationResponsePreprocessor responsePreprocessor = mock(OperationResponsePreprocessor.class); given(this.requestConverter.convert(this.request)).willReturn(this.operationRequest); diff --git a/spring-restdocs-core/src/test/java/org/springframework/restdocs/cli/ConcatenatingCommandFormatterTests.java b/spring-restdocs-core/src/test/java/org/springframework/restdocs/cli/ConcatenatingCommandFormatterTests.java index 44bd1a43..7af23e99 100644 --- a/spring-restdocs-core/src/test/java/org/springframework/restdocs/cli/ConcatenatingCommandFormatterTests.java +++ b/spring-restdocs-core/src/test/java/org/springframework/restdocs/cli/ConcatenatingCommandFormatterTests.java @@ -19,7 +19,7 @@ import java.util.Arrays; import java.util.Collections; -import org.junit.Test; +import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThat; @@ -29,27 +29,27 @@ * @author Tomasz Kopczynski * @author Andy Wilkinson */ -public class ConcatenatingCommandFormatterTests { +class ConcatenatingCommandFormatterTests { private CommandFormatter singleLineFormat = new ConcatenatingCommandFormatter(" "); @Test - public void formattingAnEmptyListProducesAnEmptyString() { + void formattingAnEmptyListProducesAnEmptyString() { assertThat(this.singleLineFormat.format(Collections.emptyList())).isEqualTo(""); } @Test - public void formattingNullProducesAnEmptyString() { + void formattingNullProducesAnEmptyString() { assertThat(this.singleLineFormat.format(null)).isEqualTo(""); } @Test - public void formattingASingleElement() { + void formattingASingleElement() { assertThat(this.singleLineFormat.format(Collections.singletonList("alpha"))).isEqualTo(" alpha"); } @Test - public void formattingMultipleElements() { + void formattingMultipleElements() { assertThat(this.singleLineFormat.format(Arrays.asList("alpha", "bravo"))).isEqualTo(" alpha bravo"); } diff --git a/spring-restdocs-core/src/test/java/org/springframework/restdocs/cli/CurlRequestSnippetTests.java b/spring-restdocs-core/src/test/java/org/springframework/restdocs/cli/CurlRequestSnippetTests.java index 2dd7b55c..cd413bed 100644 --- a/spring-restdocs-core/src/test/java/org/springframework/restdocs/cli/CurlRequestSnippetTests.java +++ b/spring-restdocs-core/src/test/java/org/springframework/restdocs/cli/CurlRequestSnippetTests.java @@ -19,14 +19,11 @@ import java.io.IOException; import java.util.Base64; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; - import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; -import org.springframework.restdocs.AbstractSnippetTests; -import org.springframework.restdocs.templates.TemplateFormat; +import org.springframework.restdocs.testfixtures.jupiter.AssertableSnippets; +import org.springframework.restdocs.testfixtures.jupiter.OperationBuilder; +import org.springframework.restdocs.testfixtures.jupiter.RenderedSnippetTest; import static org.assertj.core.api.Assertions.assertThat; @@ -40,200 +37,209 @@ * @author Paul-Christian Volkmer * @author Tomasz Kopczynski */ -@RunWith(Parameterized.class) -public class CurlRequestSnippetTests extends AbstractSnippetTests { +class CurlRequestSnippetTests { private CommandFormatter commandFormatter = CliDocumentation.singleLineFormat(); - public CurlRequestSnippetTests(String name, TemplateFormat templateFormat) { - super(name, templateFormat); - } - - @Test - public void getRequest() throws IOException { + @RenderedSnippetTest + void getRequest(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException { new CurlRequestSnippet(this.commandFormatter) - .document(this.operationBuilder.request("http://localhost/foo").build()); - assertThat(this.generatedSnippets.curlRequest()) - .is(codeBlock("bash").withContent("$ curl 'http://localhost/foo' -i -X GET")); + .document(operationBuilder.request("http://localhost/foo").build()); + assertThat(snippets.curlRequest()).isCodeBlock( + (codeBlock) -> codeBlock.withLanguage("bash").content("$ curl 'http://localhost/foo' -i -X GET")); } - @Test - public void nonGetRequest() throws IOException { + @RenderedSnippetTest + void nonGetRequest(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException { new CurlRequestSnippet(this.commandFormatter) - .document(this.operationBuilder.request("http://localhost/foo").method("POST").build()); - assertThat(this.generatedSnippets.curlRequest()) - .is(codeBlock("bash").withContent("$ curl 'http://localhost/foo' -i -X POST")); + .document(operationBuilder.request("http://localhost/foo").method("POST").build()); + assertThat(snippets.curlRequest()).isCodeBlock( + (codeBlock) -> codeBlock.withLanguage("bash").content("$ curl 'http://localhost/foo' -i -X POST")); } - @Test - public void requestWithContent() throws IOException { + @RenderedSnippetTest + void requestWithContent(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException { new CurlRequestSnippet(this.commandFormatter) - .document(this.operationBuilder.request("http://localhost/foo").content("content").build()); - assertThat(this.generatedSnippets.curlRequest()) - .is(codeBlock("bash").withContent("$ curl 'http://localhost/foo' -i -X GET -d 'content'")); + .document(operationBuilder.request("http://localhost/foo").content("content").build()); + assertThat(snippets.curlRequest()).isCodeBlock((codeBlock) -> codeBlock.withLanguage("bash") + .content("$ curl 'http://localhost/foo' -i -X GET -d 'content'")); } - @Test - public void getRequestWithQueryString() throws IOException { + @RenderedSnippetTest + void getRequestWithQueryString(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException { new CurlRequestSnippet(this.commandFormatter) - .document(this.operationBuilder.request("http://localhost/foo?param=value").build()); - assertThat(this.generatedSnippets.curlRequest()) - .is(codeBlock("bash").withContent("$ curl 'http://localhost/foo?param=value' -i -X GET")); + .document(operationBuilder.request("http://localhost/foo?param=value").build()); + assertThat(snippets.curlRequest()).isCodeBlock((codeBlock) -> codeBlock.withLanguage("bash") + .content("$ curl 'http://localhost/foo?param=value' -i -X GET")); } - @Test - public void getRequestWithQueryStringWithNoValue() throws IOException { + @RenderedSnippetTest + void getRequestWithQueryStringWithNoValue(OperationBuilder operationBuilder, AssertableSnippets snippets) + throws IOException { new CurlRequestSnippet(this.commandFormatter) - .document(this.operationBuilder.request("http://localhost/foo?param").build()); - assertThat(this.generatedSnippets.curlRequest()) - .is(codeBlock("bash").withContent("$ curl 'http://localhost/foo?param' -i -X GET")); + .document(operationBuilder.request("http://localhost/foo?param").build()); + assertThat(snippets.curlRequest()).isCodeBlock( + (codeBlock) -> codeBlock.withLanguage("bash").content("$ curl 'http://localhost/foo?param' -i -X GET")); } - @Test - public void postRequestWithQueryString() throws IOException { + @RenderedSnippetTest + void postRequestWithQueryString(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException { new CurlRequestSnippet(this.commandFormatter) - .document(this.operationBuilder.request("http://localhost/foo?param=value").method("POST").build()); - assertThat(this.generatedSnippets.curlRequest()) - .is(codeBlock("bash").withContent("$ curl 'http://localhost/foo?param=value' -i -X POST")); + .document(operationBuilder.request("http://localhost/foo?param=value").method("POST").build()); + assertThat(snippets.curlRequest()).isCodeBlock((codeBlock) -> codeBlock.withLanguage("bash") + .content("$ curl 'http://localhost/foo?param=value' -i -X POST")); } - @Test - public void postRequestWithQueryStringWithNoValue() throws IOException { + @RenderedSnippetTest + void postRequestWithQueryStringWithNoValue(OperationBuilder operationBuilder, AssertableSnippets snippets) + throws IOException { new CurlRequestSnippet(this.commandFormatter) - .document(this.operationBuilder.request("http://localhost/foo?param").method("POST").build()); - assertThat(this.generatedSnippets.curlRequest()) - .is(codeBlock("bash").withContent("$ curl 'http://localhost/foo?param' -i -X POST")); + .document(operationBuilder.request("http://localhost/foo?param").method("POST").build()); + assertThat(snippets.curlRequest()).isCodeBlock((codeBlock) -> codeBlock.withLanguage("bash") + .content("$ curl 'http://localhost/foo?param' -i -X POST")); } - @Test - public void postRequestWithOneParameter() throws IOException { + @RenderedSnippetTest + void postRequestWithOneParameter(OperationBuilder operationBuilder, AssertableSnippets snippets) + throws IOException { new CurlRequestSnippet(this.commandFormatter) - .document(this.operationBuilder.request("http://localhost/foo").method("POST").content("k1=v1").build()); - assertThat(this.generatedSnippets.curlRequest()) - .is(codeBlock("bash").withContent("$ curl 'http://localhost/foo' -i -X POST -d 'k1=v1'")); + .document(operationBuilder.request("http://localhost/foo").method("POST").content("k1=v1").build()); + assertThat(snippets.curlRequest()).isCodeBlock((codeBlock) -> codeBlock.withLanguage("bash") + .content("$ curl 'http://localhost/foo' -i -X POST -d 'k1=v1'")); } - @Test - public void postRequestWithOneParameterAndExplicitContentType() throws IOException { - new CurlRequestSnippet(this.commandFormatter).document(this.operationBuilder.request("http://localhost/foo") + @RenderedSnippetTest + void postRequestWithOneParameterAndExplicitContentType(OperationBuilder operationBuilder, + AssertableSnippets snippets) throws IOException { + new CurlRequestSnippet(this.commandFormatter).document(operationBuilder.request("http://localhost/foo") .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED_VALUE) .method("POST") .content("k1=v1") .build()); - assertThat(this.generatedSnippets.curlRequest()) - .is(codeBlock("bash").withContent("$ curl 'http://localhost/foo' -i -X POST -d 'k1=v1'")); + assertThat(snippets.curlRequest()).isCodeBlock((codeBlock) -> codeBlock.withLanguage("bash") + .content("$ curl 'http://localhost/foo' -i -X POST -d 'k1=v1'")); } - @Test - public void postRequestWithOneParameterWithNoValue() throws IOException { + @RenderedSnippetTest + void postRequestWithOneParameterWithNoValue(OperationBuilder operationBuilder, AssertableSnippets snippets) + throws IOException { new CurlRequestSnippet(this.commandFormatter) - .document(this.operationBuilder.request("http://localhost/foo").method("POST").content("k1=").build()); - assertThat(this.generatedSnippets.curlRequest()) - .is(codeBlock("bash").withContent("$ curl 'http://localhost/foo' -i -X POST -d 'k1='")); + .document(operationBuilder.request("http://localhost/foo").method("POST").content("k1=").build()); + assertThat(snippets.curlRequest()).isCodeBlock((codeBlock) -> codeBlock.withLanguage("bash") + .content("$ curl 'http://localhost/foo' -i -X POST -d 'k1='")); } - @Test - public void postRequestWithMultipleParameters() throws IOException { - new CurlRequestSnippet(this.commandFormatter).document(this.operationBuilder.request("http://localhost/foo") + @RenderedSnippetTest + void postRequestWithMultipleParameters(OperationBuilder operationBuilder, AssertableSnippets snippets) + throws IOException { + new CurlRequestSnippet(this.commandFormatter).document(operationBuilder.request("http://localhost/foo") .method("POST") .content("k1=v1&k1=v1-bis&k2=v2") .build()); - assertThat(this.generatedSnippets.curlRequest()).is(codeBlock("bash") - .withContent("$ curl 'http://localhost/foo' -i -X POST" + " -d 'k1=v1&k1=v1-bis&k2=v2'")); + assertThat(snippets.curlRequest()).isCodeBlock((codeBlock) -> codeBlock.withLanguage("bash") + .content("$ curl 'http://localhost/foo' -i -X POST" + " -d 'k1=v1&k1=v1-bis&k2=v2'")); } - @Test - public void postRequestWithUrlEncodedParameter() throws IOException { + @RenderedSnippetTest + void postRequestWithUrlEncodedParameter(OperationBuilder operationBuilder, AssertableSnippets snippets) + throws IOException { new CurlRequestSnippet(this.commandFormatter) - .document(this.operationBuilder.request("http://localhost/foo").method("POST").content("k1=a%26b").build()); - assertThat(this.generatedSnippets.curlRequest()) - .is(codeBlock("bash").withContent("$ curl 'http://localhost/foo' -i -X POST -d 'k1=a%26b'")); + .document(operationBuilder.request("http://localhost/foo").method("POST").content("k1=a%26b").build()); + assertThat(snippets.curlRequest()).isCodeBlock((codeBlock) -> codeBlock.withLanguage("bash") + .content("$ curl 'http://localhost/foo' -i -X POST -d 'k1=a%26b'")); } - @Test - public void postRequestWithJsonData() throws IOException { - new CurlRequestSnippet(this.commandFormatter).document(this.operationBuilder.request("http://localhost/foo") + @RenderedSnippetTest + void postRequestWithJsonData(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException { + new CurlRequestSnippet(this.commandFormatter).document(operationBuilder.request("http://localhost/foo") .method("POST") .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) .content("{\"a\":\"alpha\"}") .build()); - assertThat(this.generatedSnippets.curlRequest()).is(codeBlock("bash").withContent( - "$ curl 'http://localhost/foo' -i -X POST -H 'Content-Type: application/json' -d '{\"a\":\"alpha\"}'")); + assertThat(snippets.curlRequest()).isCodeBlock((codeBlock) -> codeBlock.withLanguage("bash") + .content( + "$ curl 'http://localhost/foo' -i -X POST -H 'Content-Type: application/json' -d '{\"a\":\"alpha\"}'")); } - @Test - public void putRequestWithOneParameter() throws IOException { + @RenderedSnippetTest + void putRequestWithOneParameter(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException { new CurlRequestSnippet(this.commandFormatter) - .document(this.operationBuilder.request("http://localhost/foo").method("PUT").content("k1=v1").build()); - assertThat(this.generatedSnippets.curlRequest()) - .is(codeBlock("bash").withContent("$ curl 'http://localhost/foo' -i -X PUT -d 'k1=v1'")); + .document(operationBuilder.request("http://localhost/foo").method("PUT").content("k1=v1").build()); + assertThat(snippets.curlRequest()).isCodeBlock((codeBlock) -> codeBlock.withLanguage("bash") + .content("$ curl 'http://localhost/foo' -i -X PUT -d 'k1=v1'")); } - @Test - public void putRequestWithMultipleParameters() throws IOException { - new CurlRequestSnippet(this.commandFormatter).document(this.operationBuilder.request("http://localhost/foo") + @RenderedSnippetTest + void putRequestWithMultipleParameters(OperationBuilder operationBuilder, AssertableSnippets snippets) + throws IOException { + new CurlRequestSnippet(this.commandFormatter).document(operationBuilder.request("http://localhost/foo") .method("PUT") .content("k1=v1&k1=v1-bis&k2=v2") .build()); - assertThat(this.generatedSnippets.curlRequest()).is(codeBlock("bash") - .withContent("$ curl 'http://localhost/foo' -i -X PUT" + " -d 'k1=v1&k1=v1-bis&k2=v2'")); + assertThat(snippets.curlRequest()).isCodeBlock((codeBlock) -> codeBlock.withLanguage("bash") + .content("$ curl 'http://localhost/foo' -i -X PUT" + " -d 'k1=v1&k1=v1-bis&k2=v2'")); } - @Test - public void putRequestWithUrlEncodedParameter() throws IOException { + @RenderedSnippetTest + void putRequestWithUrlEncodedParameter(OperationBuilder operationBuilder, AssertableSnippets snippets) + throws IOException { new CurlRequestSnippet(this.commandFormatter) - .document(this.operationBuilder.request("http://localhost/foo").method("PUT").content("k1=a%26b").build()); - assertThat(this.generatedSnippets.curlRequest()) - .is(codeBlock("bash").withContent("$ curl 'http://localhost/foo' -i -X PUT -d 'k1=a%26b'")); + .document(operationBuilder.request("http://localhost/foo").method("PUT").content("k1=a%26b").build()); + assertThat(snippets.curlRequest()).isCodeBlock((codeBlock) -> codeBlock.withLanguage("bash") + .content("$ curl 'http://localhost/foo' -i -X PUT -d 'k1=a%26b'")); } - @Test - public void requestWithHeaders() throws IOException { - new CurlRequestSnippet(this.commandFormatter).document(this.operationBuilder.request("http://localhost/foo") + @RenderedSnippetTest + void requestWithHeaders(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException { + new CurlRequestSnippet(this.commandFormatter).document(operationBuilder.request("http://localhost/foo") .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) .header("a", "alpha") .build()); - assertThat(this.generatedSnippets.curlRequest()).is(codeBlock("bash").withContent( - "$ curl 'http://localhost/foo' -i -X GET" + " -H 'Content-Type: application/json' -H 'a: alpha'")); + assertThat(snippets.curlRequest()).isCodeBlock((codeBlock) -> codeBlock.withLanguage("bash") + .content("$ curl 'http://localhost/foo' -i -X GET" + " -H 'Content-Type: application/json' -H 'a: alpha'")); } - @Test - public void requestWithHeadersMultiline() throws IOException { + @RenderedSnippetTest + void requestWithHeadersMultiline(OperationBuilder operationBuilder, AssertableSnippets snippets) + throws IOException { new CurlRequestSnippet(CliDocumentation.multiLineFormat()) - .document(this.operationBuilder.request("http://localhost/foo") + .document(operationBuilder.request("http://localhost/foo") .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) .header("a", "alpha") .build()); - assertThat(this.generatedSnippets.curlRequest()) - .is(codeBlock("bash").withContent(String.format("$ curl 'http://localhost/foo' -i -X GET \\%n" + assertThat(snippets.curlRequest()).isCodeBlock((codeBlock) -> codeBlock.withLanguage("bash") + .content(String.format("$ curl 'http://localhost/foo' -i -X GET \\%n" + " -H 'Content-Type: application/json' \\%n" + " -H 'a: alpha'"))); } - @Test - public void requestWithCookies() throws IOException { - new CurlRequestSnippet(this.commandFormatter).document(this.operationBuilder.request("http://localhost/foo") + @RenderedSnippetTest + void requestWithCookies(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException { + new CurlRequestSnippet(this.commandFormatter).document(operationBuilder.request("http://localhost/foo") .cookie("name1", "value1") .cookie("name2", "value2") .build()); - assertThat(this.generatedSnippets.curlRequest()).is(codeBlock("bash") - .withContent("$ curl 'http://localhost/foo' -i -X GET" + " --cookie 'name1=value1;name2=value2'")); + assertThat(snippets.curlRequest()).isCodeBlock((codeBlock) -> codeBlock.withLanguage("bash") + .content("$ curl 'http://localhost/foo' -i -X GET" + " --cookie 'name1=value1;name2=value2'")); } - @Test - public void multipartPostWithNoSubmittedFileName() throws IOException { - new CurlRequestSnippet(this.commandFormatter).document(this.operationBuilder.request("http://localhost/upload") + @RenderedSnippetTest + void multipartPostWithNoSubmittedFileName(OperationBuilder operationBuilder, AssertableSnippets snippets) + throws IOException { + new CurlRequestSnippet(this.commandFormatter).document(operationBuilder.request("http://localhost/upload") .method("POST") .header(HttpHeaders.CONTENT_TYPE, MediaType.MULTIPART_FORM_DATA_VALUE) .part("metadata", "{\"description\": \"foo\"}".getBytes()) .build()); String expectedContent = "$ curl 'http://localhost/upload' -i -X POST -H " + "'Content-Type: multipart/form-data' -F " + "'metadata={\"description\": \"foo\"}'"; - assertThat(this.generatedSnippets.curlRequest()).is(codeBlock("bash").withContent(expectedContent)); + assertThat(snippets.curlRequest()) + .isCodeBlock((codeBlock) -> codeBlock.withLanguage("bash").content(expectedContent)); } - @Test - public void multipartPostWithContentType() throws IOException { - new CurlRequestSnippet(this.commandFormatter).document(this.operationBuilder.request("http://localhost/upload") + @RenderedSnippetTest + void multipartPostWithContentType(OperationBuilder operationBuilder, AssertableSnippets snippets) + throws IOException { + new CurlRequestSnippet(this.commandFormatter).document(operationBuilder.request("http://localhost/upload") .method("POST") .header(HttpHeaders.CONTENT_TYPE, MediaType.MULTIPART_FORM_DATA_VALUE) .part("image", new byte[0]) @@ -242,12 +248,13 @@ public void multipartPostWithContentType() throws IOException { .build()); String expectedContent = "$ curl 'http://localhost/upload' -i -X POST -H " + "'Content-Type: multipart/form-data' -F " + "'image=@documents/images/example.png;type=image/png'"; - assertThat(this.generatedSnippets.curlRequest()).is(codeBlock("bash").withContent(expectedContent)); + assertThat(snippets.curlRequest()) + .isCodeBlock((codeBlock) -> codeBlock.withLanguage("bash").content(expectedContent)); } - @Test - public void multipartPost() throws IOException { - new CurlRequestSnippet(this.commandFormatter).document(this.operationBuilder.request("http://localhost/upload") + @RenderedSnippetTest + void multipartPost(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException { + new CurlRequestSnippet(this.commandFormatter).document(operationBuilder.request("http://localhost/upload") .method("POST") .header(HttpHeaders.CONTENT_TYPE, MediaType.MULTIPART_FORM_DATA_VALUE) .part("image", new byte[0]) @@ -255,36 +262,38 @@ public void multipartPost() throws IOException { .build()); String expectedContent = "$ curl 'http://localhost/upload' -i -X POST -H " + "'Content-Type: multipart/form-data' -F " + "'image=@documents/images/example.png'"; - assertThat(this.generatedSnippets.curlRequest()).is(codeBlock("bash").withContent(expectedContent)); + assertThat(snippets.curlRequest()) + .isCodeBlock((codeBlock) -> codeBlock.withLanguage("bash").content(expectedContent)); } - @Test - public void basicAuthCredentialsAreSuppliedUsingUserOption() throws IOException { - new CurlRequestSnippet(this.commandFormatter).document(this.operationBuilder.request("http://localhost/foo") + @RenderedSnippetTest + void basicAuthCredentialsAreSuppliedUsingUserOption(OperationBuilder operationBuilder, AssertableSnippets snippets) + throws IOException { + new CurlRequestSnippet(this.commandFormatter).document(operationBuilder.request("http://localhost/foo") .header(HttpHeaders.AUTHORIZATION, "Basic " + Base64.getEncoder().encodeToString("user:secret".getBytes())) .build()); - assertThat(this.generatedSnippets.curlRequest()) - .is(codeBlock("bash").withContent("$ curl 'http://localhost/foo' -i -u 'user:secret' -X GET")); + assertThat(snippets.curlRequest()).isCodeBlock((codeBlock) -> codeBlock.withLanguage("bash") + .content("$ curl 'http://localhost/foo' -i -u 'user:secret' -X GET")); } - @Test - public void customAttributes() throws IOException { - new CurlRequestSnippet(this.commandFormatter).document(this.operationBuilder.request("http://localhost/foo") + @RenderedSnippetTest + void customAttributes(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException { + new CurlRequestSnippet(this.commandFormatter).document(operationBuilder.request("http://localhost/foo") .header(HttpHeaders.HOST, "api.example.com") .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) .header("a", "alpha") .build()); - assertThat(this.generatedSnippets.curlRequest()) - .is(codeBlock("bash").withContent("$ curl 'http://localhost/foo' -i -X GET -H 'Host: api.example.com'" + assertThat(snippets.curlRequest()).isCodeBlock((codeBlock) -> codeBlock.withLanguage("bash") + .content("$ curl 'http://localhost/foo' -i -X GET -H 'Host: api.example.com'" + " -H 'Content-Type: application/json' -H 'a: alpha'")); } - @Test - public void deleteWithQueryString() throws IOException { + @RenderedSnippetTest + void deleteWithQueryString(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException { new CurlRequestSnippet(this.commandFormatter) - .document(this.operationBuilder.request("http://localhost/foo?a=alpha&b=bravo").method("DELETE").build()); - assertThat(this.generatedSnippets.curlRequest()) - .is(codeBlock("bash").withContent("$ curl 'http://localhost/foo?a=alpha&b=bravo' -i " + "-X DELETE")); + .document(operationBuilder.request("http://localhost/foo?a=alpha&b=bravo").method("DELETE").build()); + assertThat(snippets.curlRequest()).isCodeBlock((codeBlock) -> codeBlock.withLanguage("bash") + .content("$ curl 'http://localhost/foo?a=alpha&b=bravo' -i " + "-X DELETE")); } } diff --git a/spring-restdocs-core/src/test/java/org/springframework/restdocs/cli/HttpieRequestSnippetTests.java b/spring-restdocs-core/src/test/java/org/springframework/restdocs/cli/HttpieRequestSnippetTests.java index 1f444121..36ba852e 100644 --- a/spring-restdocs-core/src/test/java/org/springframework/restdocs/cli/HttpieRequestSnippetTests.java +++ b/spring-restdocs-core/src/test/java/org/springframework/restdocs/cli/HttpieRequestSnippetTests.java @@ -19,14 +19,11 @@ import java.io.IOException; import java.util.Base64; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; - import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; -import org.springframework.restdocs.AbstractSnippetTests; -import org.springframework.restdocs.templates.TemplateFormat; +import org.springframework.restdocs.testfixtures.jupiter.AssertableSnippets; +import org.springframework.restdocs.testfixtures.jupiter.OperationBuilder; +import org.springframework.restdocs.testfixtures.jupiter.RenderedSnippetTest; import static org.assertj.core.api.Assertions.assertThat; @@ -41,249 +38,257 @@ * @author Raman Gupta * @author Tomasz Kopczynski */ -@RunWith(Parameterized.class) -public class HttpieRequestSnippetTests extends AbstractSnippetTests { +class HttpieRequestSnippetTests { private CommandFormatter commandFormatter = CliDocumentation.singleLineFormat(); - public HttpieRequestSnippetTests(String name, TemplateFormat templateFormat) { - super(name, templateFormat); - } - - @Test - public void getRequest() throws IOException { + @RenderedSnippetTest + void getRequest(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException { new HttpieRequestSnippet(this.commandFormatter) - .document(this.operationBuilder.request("http://localhost/foo").build()); - assertThat(this.generatedSnippets.httpieRequest()) - .is(codeBlock("bash").withContent("$ http GET 'http://localhost/foo'")); + .document(operationBuilder.request("http://localhost/foo").build()); + assertThat(snippets.httpieRequest()) + .isCodeBlock((codeBlock) -> codeBlock.withLanguage("bash").content("$ http GET 'http://localhost/foo'")); } - @Test - public void nonGetRequest() throws IOException { + @RenderedSnippetTest + void nonGetRequest(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException { new HttpieRequestSnippet(this.commandFormatter) - .document(this.operationBuilder.request("http://localhost/foo").method("POST").build()); - assertThat(this.generatedSnippets.httpieRequest()) - .is(codeBlock("bash").withContent("$ http POST 'http://localhost/foo'")); + .document(operationBuilder.request("http://localhost/foo").method("POST").build()); + assertThat(snippets.httpieRequest()) + .isCodeBlock((codeBlock) -> codeBlock.withLanguage("bash").content("$ http POST 'http://localhost/foo'")); } - @Test - public void requestWithContent() throws IOException { + @RenderedSnippetTest + void requestWithContent(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException { new HttpieRequestSnippet(this.commandFormatter) - .document(this.operationBuilder.request("http://localhost/foo").content("content").build()); - assertThat(this.generatedSnippets.httpieRequest()) - .is(codeBlock("bash").withContent("$ echo 'content' | http GET 'http://localhost/foo'")); + .document(operationBuilder.request("http://localhost/foo").content("content").build()); + assertThat(snippets.httpieRequest()).isCodeBlock((codeBlock) -> codeBlock.withLanguage("bash") + .content("$ echo 'content' | http GET 'http://localhost/foo'")); } - @Test - public void getRequestWithQueryString() throws IOException { + @RenderedSnippetTest + void getRequestWithQueryString(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException { new HttpieRequestSnippet(this.commandFormatter) - .document(this.operationBuilder.request("http://localhost/foo?param=value").build()); - assertThat(this.generatedSnippets.httpieRequest()) - .is(codeBlock("bash").withContent("$ http GET 'http://localhost/foo?param=value'")); + .document(operationBuilder.request("http://localhost/foo?param=value").build()); + assertThat(snippets.httpieRequest()).isCodeBlock( + (codeBlock) -> codeBlock.withLanguage("bash").content("$ http GET 'http://localhost/foo?param=value'")); } - @Test - public void getRequestWithQueryStringWithNoValue() throws IOException { + @RenderedSnippetTest + void getRequestWithQueryStringWithNoValue(OperationBuilder operationBuilder, AssertableSnippets snippets) + throws IOException { new HttpieRequestSnippet(this.commandFormatter) - .document(this.operationBuilder.request("http://localhost/foo?param").build()); - assertThat(this.generatedSnippets.httpieRequest()) - .is(codeBlock("bash").withContent("$ http GET 'http://localhost/foo?param'")); + .document(operationBuilder.request("http://localhost/foo?param").build()); + assertThat(snippets.httpieRequest()).isCodeBlock( + (codeBlock) -> codeBlock.withLanguage("bash").content("$ http GET 'http://localhost/foo?param'")); } - @Test - public void postRequestWithQueryString() throws IOException { + @RenderedSnippetTest + void postRequestWithQueryString(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException { new HttpieRequestSnippet(this.commandFormatter) - .document(this.operationBuilder.request("http://localhost/foo?param=value").method("POST").build()); - assertThat(this.generatedSnippets.httpieRequest()) - .is(codeBlock("bash").withContent("$ http POST 'http://localhost/foo?param=value'")); + .document(operationBuilder.request("http://localhost/foo?param=value").method("POST").build()); + assertThat(snippets.httpieRequest()).isCodeBlock((codeBlock) -> codeBlock.withLanguage("bash") + .content("$ http POST 'http://localhost/foo?param=value'")); } - @Test - public void postRequestWithQueryStringWithNoValue() throws IOException { + @RenderedSnippetTest + void postRequestWithQueryStringWithNoValue(OperationBuilder operationBuilder, AssertableSnippets snippets) + throws IOException { new HttpieRequestSnippet(this.commandFormatter) - .document(this.operationBuilder.request("http://localhost/foo?param").method("POST").build()); - assertThat(this.generatedSnippets.httpieRequest()) - .is(codeBlock("bash").withContent("$ http POST 'http://localhost/foo?param'")); + .document(operationBuilder.request("http://localhost/foo?param").method("POST").build()); + assertThat(snippets.httpieRequest()).isCodeBlock( + (codeBlock) -> codeBlock.withLanguage("bash").content("$ http POST 'http://localhost/foo?param'")); } - @Test - public void postRequestWithOneParameter() throws IOException { - new HttpieRequestSnippet(this.commandFormatter).document(this.operationBuilder.request("http://localhost/foo") + @RenderedSnippetTest + void postRequestWithOneParameter(OperationBuilder operationBuilder, AssertableSnippets snippets) + throws IOException { + new HttpieRequestSnippet(this.commandFormatter).document(operationBuilder.request("http://localhost/foo") .method("POST") .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED_VALUE) .content("k1=v1") .build()); - assertThat(this.generatedSnippets.httpieRequest()) - .is(codeBlock("bash").withContent("$ http --form POST 'http://localhost/foo' 'k1=v1'")); + assertThat(snippets.httpieRequest()).isCodeBlock((codeBlock) -> codeBlock.withLanguage("bash") + .content("$ http --form POST 'http://localhost/foo' 'k1=v1'")); } - @Test - public void postRequestWithOneParameterWithNoValue() throws IOException { - new HttpieRequestSnippet(this.commandFormatter).document(this.operationBuilder.request("http://localhost/foo") + @RenderedSnippetTest + void postRequestWithOneParameterWithNoValue(OperationBuilder operationBuilder, AssertableSnippets snippets) + throws IOException { + new HttpieRequestSnippet(this.commandFormatter).document(operationBuilder.request("http://localhost/foo") .method("POST") .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED_VALUE) .content("k1") .build()); - assertThat(this.generatedSnippets.httpieRequest()) - .is(codeBlock("bash").withContent("$ http --form POST 'http://localhost/foo' 'k1='")); + assertThat(snippets.httpieRequest()).isCodeBlock((codeBlock) -> codeBlock.withLanguage("bash") + .content("$ http --form POST 'http://localhost/foo' 'k1='")); } - @Test - public void postRequestWithMultipleParameters() throws IOException { - new HttpieRequestSnippet(this.commandFormatter).document(this.operationBuilder.request("http://localhost/foo") + @RenderedSnippetTest + void postRequestWithMultipleParameters(OperationBuilder operationBuilder, AssertableSnippets snippets) + throws IOException { + new HttpieRequestSnippet(this.commandFormatter).document(operationBuilder.request("http://localhost/foo") .method("POST") .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED_VALUE) .content("k1=v1&k1=v1-bis&k2=v2") .build()); - assertThat(this.generatedSnippets.httpieRequest()) - .is(codeBlock("bash").withContent("$ http --form POST 'http://localhost/foo' 'k1=v1' 'k1=v1-bis' 'k2=v2'")); + assertThat(snippets.httpieRequest()).isCodeBlock((codeBlock) -> codeBlock.withLanguage("bash") + .content("$ http --form POST 'http://localhost/foo' 'k1=v1' 'k1=v1-bis' 'k2=v2'")); } - @Test - public void postRequestWithUrlEncodedParameter() throws IOException { - new HttpieRequestSnippet(this.commandFormatter).document(this.operationBuilder.request("http://localhost/foo") + @RenderedSnippetTest + void postRequestWithUrlEncodedParameter(OperationBuilder operationBuilder, AssertableSnippets snippets) + throws IOException { + new HttpieRequestSnippet(this.commandFormatter).document(operationBuilder.request("http://localhost/foo") .method("POST") .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED_VALUE) .content("k1=a%26b") .build()); - assertThat(this.generatedSnippets.httpieRequest()) - .is(codeBlock("bash").withContent("$ http --form POST 'http://localhost/foo' 'k1=a&b'")); + assertThat(snippets.httpieRequest()).isCodeBlock((codeBlock) -> codeBlock.withLanguage("bash") + .content("$ http --form POST 'http://localhost/foo' 'k1=a&b'")); } - @Test - public void putRequestWithOneParameter() throws IOException { - new HttpieRequestSnippet(this.commandFormatter).document(this.operationBuilder.request("http://localhost/foo") + @RenderedSnippetTest + void putRequestWithOneParameter(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException { + new HttpieRequestSnippet(this.commandFormatter).document(operationBuilder.request("http://localhost/foo") .method("PUT") .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED_VALUE) .content("k1=v1") .build()); - assertThat(this.generatedSnippets.httpieRequest()) - .is(codeBlock("bash").withContent("$ http --form PUT 'http://localhost/foo' 'k1=v1'")); + assertThat(snippets.httpieRequest()).isCodeBlock((codeBlock) -> codeBlock.withLanguage("bash") + .content("$ http --form PUT 'http://localhost/foo' 'k1=v1'")); } - @Test - public void putRequestWithMultipleParameters() throws IOException { - new HttpieRequestSnippet(this.commandFormatter).document(this.operationBuilder.request("http://localhost/foo") + @RenderedSnippetTest + void putRequestWithMultipleParameters(OperationBuilder operationBuilder, AssertableSnippets snippets) + throws IOException { + new HttpieRequestSnippet(this.commandFormatter).document(operationBuilder.request("http://localhost/foo") .method("PUT") .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED_VALUE) .content("k1=v1&k1=v1-bis&k2=v2") .build()); - assertThat(this.generatedSnippets.httpieRequest()).is(codeBlock("bash") - .withContent("$ http --form PUT 'http://localhost/foo'" + " 'k1=v1' 'k1=v1-bis' 'k2=v2'")); + assertThat(snippets.httpieRequest()).isCodeBlock((codeBlock) -> codeBlock.withLanguage("bash") + .content("$ http --form PUT 'http://localhost/foo'" + " 'k1=v1' 'k1=v1-bis' 'k2=v2'")); } - @Test - public void putRequestWithUrlEncodedParameter() throws IOException { - new HttpieRequestSnippet(this.commandFormatter).document(this.operationBuilder.request("http://localhost/foo") + @RenderedSnippetTest + void putRequestWithUrlEncodedParameter(OperationBuilder operationBuilder, AssertableSnippets snippets) + throws IOException { + new HttpieRequestSnippet(this.commandFormatter).document(operationBuilder.request("http://localhost/foo") .method("PUT") .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED_VALUE) .content("k1=a%26b") .build()); - assertThat(this.generatedSnippets.httpieRequest()) - .is(codeBlock("bash").withContent("$ http --form PUT 'http://localhost/foo' 'k1=a&b'")); + assertThat(snippets.httpieRequest()).isCodeBlock((codeBlock) -> codeBlock.withLanguage("bash") + .content("$ http --form PUT 'http://localhost/foo' 'k1=a&b'")); } - @Test - public void requestWithHeaders() throws IOException { - new HttpieRequestSnippet(this.commandFormatter).document(this.operationBuilder.request("http://localhost/foo") + @RenderedSnippetTest + void requestWithHeaders(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException { + new HttpieRequestSnippet(this.commandFormatter).document(operationBuilder.request("http://localhost/foo") .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) .header("a", "alpha") .build()); - assertThat(this.generatedSnippets.httpieRequest()).is(codeBlock("bash") - .withContent("$ http GET 'http://localhost/foo'" + " 'Content-Type:application/json' 'a:alpha'")); + assertThat(snippets.httpieRequest()).isCodeBlock((codeBlock) -> codeBlock.withLanguage("bash") + .content(("$ http GET 'http://localhost/foo'" + " 'Content-Type:application/json' 'a:alpha'"))); } - @Test - public void requestWithHeadersMultiline() throws IOException { + @RenderedSnippetTest + void requestWithHeadersMultiline(OperationBuilder operationBuilder, AssertableSnippets snippets) + throws IOException { new HttpieRequestSnippet(CliDocumentation.multiLineFormat()) - .document(this.operationBuilder.request("http://localhost/foo") + .document(operationBuilder.request("http://localhost/foo") .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) .header("a", "alpha") .build()); - assertThat(this.generatedSnippets.httpieRequest()).is(codeBlock("bash").withContent(String.format( - "$ http GET 'http://localhost/foo' \\%n" + " 'Content-Type:application/json' \\%n 'a:alpha'"))); + assertThat(snippets.httpieRequest()).isCodeBlock((codeBlock) -> codeBlock.withLanguage("bash") + .content(String.format("$ http GET 'http://localhost/foo' \\%n" + + " 'Content-Type:application/json' \\%n 'a:alpha'"))); } - @Test - public void requestWithCookies() throws IOException { - new HttpieRequestSnippet(this.commandFormatter).document(this.operationBuilder.request("http://localhost/foo") + @RenderedSnippetTest + void requestWithCookies(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException { + new HttpieRequestSnippet(this.commandFormatter).document(operationBuilder.request("http://localhost/foo") .cookie("name1", "value1") .cookie("name2", "value2") .build()); - assertThat(this.generatedSnippets.httpieRequest()).is(codeBlock("bash") - .withContent("$ http GET 'http://localhost/foo'" + " 'Cookie:name1=value1' 'Cookie:name2=value2'")); + assertThat(snippets.httpieRequest()).isCodeBlock((codeBlock) -> codeBlock.withLanguage("bash") + .content(("$ http GET 'http://localhost/foo'" + " 'Cookie:name1=value1' 'Cookie:name2=value2'"))); } - @Test - public void multipartPostWithNoSubmittedFileName() throws IOException { - new HttpieRequestSnippet(this.commandFormatter) - .document(this.operationBuilder.request("http://localhost/upload") - .method("POST") - .header(HttpHeaders.CONTENT_TYPE, MediaType.MULTIPART_FORM_DATA_VALUE) - .part("metadata", "{\"description\": \"foo\"}".getBytes()) - .build()); + @RenderedSnippetTest + void multipartPostWithNoSubmittedFileName(OperationBuilder operationBuilder, AssertableSnippets snippets) + throws IOException { + new HttpieRequestSnippet(this.commandFormatter).document(operationBuilder.request("http://localhost/upload") + .method("POST") + .header(HttpHeaders.CONTENT_TYPE, MediaType.MULTIPART_FORM_DATA_VALUE) + .part("metadata", "{\"description\": \"foo\"}".getBytes()) + .build()); String expectedContent = "$ http --multipart POST 'http://localhost/upload'" + " 'metadata'='{\"description\": \"foo\"}'"; - assertThat(this.generatedSnippets.httpieRequest()).is(codeBlock("bash").withContent(expectedContent)); + assertThat(snippets.httpieRequest()) + .isCodeBlock((codeBlock) -> codeBlock.withLanguage("bash").content(expectedContent)); } - @Test - public void multipartPostWithContentType() throws IOException { - new HttpieRequestSnippet(this.commandFormatter) - .document(this.operationBuilder.request("http://localhost/upload") - .method("POST") - .header(HttpHeaders.CONTENT_TYPE, MediaType.MULTIPART_FORM_DATA_VALUE) - .part("image", new byte[0]) - .header(HttpHeaders.CONTENT_TYPE, MediaType.IMAGE_PNG_VALUE) - .submittedFileName("documents/images/example.png") - .build()); + @RenderedSnippetTest + void multipartPostWithContentType(OperationBuilder operationBuilder, AssertableSnippets snippets) + throws IOException { + new HttpieRequestSnippet(this.commandFormatter).document(operationBuilder.request("http://localhost/upload") + .method("POST") + .header(HttpHeaders.CONTENT_TYPE, MediaType.MULTIPART_FORM_DATA_VALUE) + .part("image", new byte[0]) + .header(HttpHeaders.CONTENT_TYPE, MediaType.IMAGE_PNG_VALUE) + .submittedFileName("documents/images/example.png") + .build()); // httpie does not yet support manually set content type by part String expectedContent = "$ http --multipart POST 'http://localhost/upload'" + " 'image'@'documents/images/example.png'"; - assertThat(this.generatedSnippets.httpieRequest()).is(codeBlock("bash").withContent(expectedContent)); + assertThat(snippets.httpieRequest()) + .isCodeBlock((codeBlock) -> codeBlock.withLanguage("bash").content(expectedContent)); } - @Test - public void multipartPost() throws IOException { - new HttpieRequestSnippet(this.commandFormatter) - .document(this.operationBuilder.request("http://localhost/upload") - .method("POST") - .header(HttpHeaders.CONTENT_TYPE, MediaType.MULTIPART_FORM_DATA_VALUE) - .part("image", new byte[0]) - .submittedFileName("documents/images/example.png") - .build()); + @RenderedSnippetTest + void multipartPost(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException { + new HttpieRequestSnippet(this.commandFormatter).document(operationBuilder.request("http://localhost/upload") + .method("POST") + .header(HttpHeaders.CONTENT_TYPE, MediaType.MULTIPART_FORM_DATA_VALUE) + .part("image", new byte[0]) + .submittedFileName("documents/images/example.png") + .build()); String expectedContent = "$ http --multipart POST 'http://localhost/upload'" + " 'image'@'documents/images/example.png'"; - assertThat(this.generatedSnippets.httpieRequest()).is(codeBlock("bash").withContent(expectedContent)); + assertThat(snippets.httpieRequest()) + .isCodeBlock((codeBlock) -> codeBlock.withLanguage("bash").content(expectedContent)); } - @Test - public void basicAuthCredentialsAreSuppliedUsingAuthOption() throws IOException { - new HttpieRequestSnippet(this.commandFormatter).document(this.operationBuilder.request("http://localhost/foo") + @RenderedSnippetTest + void basicAuthCredentialsAreSuppliedUsingAuthOption(OperationBuilder operationBuilder, AssertableSnippets snippets) + throws IOException { + new HttpieRequestSnippet(this.commandFormatter).document(operationBuilder.request("http://localhost/foo") .header(HttpHeaders.AUTHORIZATION, "Basic " + Base64.getEncoder().encodeToString("user:secret".getBytes())) .build()); - assertThat(this.generatedSnippets.httpieRequest()) - .is(codeBlock("bash").withContent("$ http --auth 'user:secret' GET 'http://localhost/foo'")); + assertThat(snippets.httpieRequest()).isCodeBlock((codeBlock) -> codeBlock.withLanguage("bash") + .content("$ http --auth 'user:secret' GET 'http://localhost/foo'")); } - @Test - public void customAttributes() throws IOException { - new HttpieRequestSnippet(this.commandFormatter).document(this.operationBuilder.request("http://localhost/foo") + @RenderedSnippetTest + void customAttributes(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException { + new HttpieRequestSnippet(this.commandFormatter).document(operationBuilder.request("http://localhost/foo") .header(HttpHeaders.HOST, "api.example.com") .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) .header("a", "alpha") .build()); - assertThat(this.generatedSnippets.httpieRequest()) - .is(codeBlock("bash").withContent("$ http GET 'http://localhost/foo' 'Host:api.example.com'" + assertThat(snippets.httpieRequest()).isCodeBlock((codeBlock) -> codeBlock.withLanguage("bash") + .content("$ http GET 'http://localhost/foo' 'Host:api.example.com'" + " 'Content-Type:application/json' 'a:alpha'")); } - @Test - public void deleteWithQueryString() throws IOException { + @RenderedSnippetTest + void deleteWithQueryString(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException { new HttpieRequestSnippet(this.commandFormatter) - .document(this.operationBuilder.request("http://localhost/foo?a=alpha&b=bravo").method("DELETE").build()); - assertThat(this.generatedSnippets.httpieRequest()) - .is(codeBlock("bash").withContent("$ http DELETE 'http://localhost/foo?a=alpha&b=bravo'")); + .document(operationBuilder.request("http://localhost/foo?a=alpha&b=bravo").method("DELETE").build()); + assertThat(snippets.httpieRequest()).isCodeBlock((codeBlock) -> codeBlock.withLanguage("bash") + .content("$ http DELETE 'http://localhost/foo?a=alpha&b=bravo'")); } } diff --git a/spring-restdocs-core/src/test/java/org/springframework/restdocs/config/RestDocumentationConfigurerTests.java b/spring-restdocs-core/src/test/java/org/springframework/restdocs/config/RestDocumentationConfigurerTests.java index b37d9614..c244fa81 100644 --- a/spring-restdocs-core/src/test/java/org/springframework/restdocs/config/RestDocumentationConfigurerTests.java +++ b/spring-restdocs-core/src/test/java/org/springframework/restdocs/config/RestDocumentationConfigurerTests.java @@ -23,7 +23,7 @@ import java.util.List; import java.util.Map; -import org.junit.Test; +import org.junit.jupiter.api.Test; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; @@ -63,13 +63,13 @@ * @author Andy Wilkinson * @author Filip Hrisafov */ -public class RestDocumentationConfigurerTests { +class RestDocumentationConfigurerTests { private final TestRestDocumentationConfigurer configurer = new TestRestDocumentationConfigurer(); @SuppressWarnings("unchecked") @Test - public void defaultConfiguration() { + void defaultConfiguration() { Map configuration = new HashMap<>(); this.configurer.apply(configuration, createContext()); assertThat(configuration).containsKey(TemplateEngine.class.getName()); @@ -102,7 +102,7 @@ public void defaultConfiguration() { } @Test - public void customTemplateEngine() { + void customTemplateEngine() { Map configuration = new HashMap<>(); TemplateEngine templateEngine = mock(TemplateEngine.class); this.configurer.templateEngine(templateEngine).apply(configuration, createContext()); @@ -110,7 +110,7 @@ public void customTemplateEngine() { } @Test - public void customWriterResolver() { + void customWriterResolver() { Map configuration = new HashMap<>(); WriterResolver writerResolver = mock(WriterResolver.class); this.configurer.writerResolver(writerResolver).apply(configuration, createContext()); @@ -118,7 +118,7 @@ public void customWriterResolver() { } @Test - public void customDefaultSnippets() { + void customDefaultSnippets() { Map configuration = new HashMap<>(); this.configurer.snippets().withDefaults(CliDocumentation.curlRequest()).apply(configuration, createContext()); assertThat(configuration).containsKey(RestDocumentationGenerator.ATTRIBUTE_NAME_DEFAULT_SNIPPETS); @@ -133,7 +133,7 @@ public void customDefaultSnippets() { @SuppressWarnings("unchecked") @Test - public void additionalDefaultSnippets() { + void additionalDefaultSnippets() { Map configuration = new HashMap<>(); Snippet snippet = mock(Snippet.class); this.configurer.snippets().withAdditionalDefaults(snippet).apply(configuration, createContext()); @@ -148,7 +148,7 @@ public void additionalDefaultSnippets() { } @Test - public void customSnippetEncoding() { + void customSnippetEncoding() { Map configuration = new HashMap<>(); this.configurer.snippets().withEncoding("ISO-8859-1"); this.configurer.apply(configuration, createContext()); @@ -162,7 +162,7 @@ public void customSnippetEncoding() { } @Test - public void customTemplateFormat() { + void customTemplateFormat() { Map configuration = new HashMap<>(); this.configurer.snippets().withTemplateFormat(TemplateFormats.markdown()).apply(configuration, createContext()); assertThat(configuration).containsKey(SnippetConfiguration.class.getName()); @@ -174,7 +174,7 @@ public void customTemplateFormat() { @SuppressWarnings("unchecked") @Test - public void asciidoctorTableCellContentLambaIsInstalledWhenUsingAsciidoctorTemplateFormat() { + void asciidoctorTableCellContentLambaIsInstalledWhenUsingAsciidoctorTemplateFormat() { Map configuration = new HashMap<>(); this.configurer.apply(configuration, createContext()); TemplateEngine templateEngine = (TemplateEngine) configuration.get(TemplateEngine.class.getName()); @@ -187,7 +187,7 @@ public void asciidoctorTableCellContentLambaIsInstalledWhenUsingAsciidoctorTempl @SuppressWarnings("unchecked") @Test - public void asciidoctorTableCellContentLambaIsNotInstalledWhenUsingNonAsciidoctorTemplateFormat() { + void asciidoctorTableCellContentLambaIsNotInstalledWhenUsingNonAsciidoctorTemplateFormat() { Map configuration = new HashMap<>(); this.configurer.snippetConfigurer.withTemplateFormat(TemplateFormats.markdown()); this.configurer.apply(configuration, createContext()); @@ -199,7 +199,7 @@ public void asciidoctorTableCellContentLambaIsNotInstalledWhenUsingNonAsciidocto } @Test - public void customDefaultOperationRequestPreprocessor() { + void customDefaultOperationRequestPreprocessor() { Map configuration = new HashMap<>(); this.configurer.operationPreprocessors() .withRequestDefaults(Preprocessors.prettyPrint(), Preprocessors.modifyHeaders().remove("Foo")) @@ -210,11 +210,11 @@ public void customDefaultOperationRequestPreprocessor() { headers.add("Foo", "value"); OperationRequest request = new OperationRequestFactory().create(URI.create("http://localhost:8080"), HttpMethod.GET, null, headers, null, Collections.emptyList()); - assertThat(preprocessor.preprocess(request).getHeaders()).doesNotContainKey("Foo"); + assertThat(preprocessor.preprocess(request).getHeaders().headerNames()).doesNotContain("Foo"); } @Test - public void customDefaultOperationResponsePreprocessor() { + void customDefaultOperationResponsePreprocessor() { Map configuration = new HashMap<>(); this.configurer.operationPreprocessors() .withResponseDefaults(Preprocessors.prettyPrint(), Preprocessors.modifyHeaders().remove("Foo")) @@ -224,7 +224,7 @@ public void customDefaultOperationResponsePreprocessor() { HttpHeaders headers = new HttpHeaders(); headers.add("Foo", "value"); OperationResponse response = new OperationResponseFactory().create(HttpStatus.OK, headers, null); - assertThat(preprocessor.preprocess(response).getHeaders()).doesNotContainKey("Foo"); + assertThat(preprocessor.preprocess(response).getHeaders().headerNames()).doesNotContain("Foo"); } private RestDocumentationContext createContext() { diff --git a/spring-restdocs-core/src/test/java/org/springframework/restdocs/constraints/ConstraintDescriptionsTests.java b/spring-restdocs-core/src/test/java/org/springframework/restdocs/constraints/ConstraintDescriptionsTests.java index 09ae4ec6..caabd48b 100644 --- a/spring-restdocs-core/src/test/java/org/springframework/restdocs/constraints/ConstraintDescriptionsTests.java +++ b/spring-restdocs-core/src/test/java/org/springframework/restdocs/constraints/ConstraintDescriptionsTests.java @@ -19,7 +19,7 @@ import java.util.Arrays; import java.util.Collections; -import org.junit.Test; +import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.BDDMockito.given; @@ -30,7 +30,7 @@ * * @author Andy Wilkinson */ -public class ConstraintDescriptionsTests { +class ConstraintDescriptionsTests { private final ConstraintResolver constraintResolver = mock(ConstraintResolver.class); @@ -41,7 +41,7 @@ public class ConstraintDescriptionsTests { this.constraintResolver, this.constraintDescriptionResolver); @Test - public void descriptionsForConstraints() { + void descriptionsForConstraints() { Constraint constraint1 = new Constraint("constraint1", Collections.emptyMap()); Constraint constraint2 = new Constraint("constraint2", Collections.emptyMap()); given(this.constraintResolver.resolveForProperty("foo", Constrained.class)) @@ -52,7 +52,7 @@ public void descriptionsForConstraints() { } @Test - public void emptyListOfDescriptionsWhenThereAreNoConstraints() { + void emptyListOfDescriptionsWhenThereAreNoConstraints() { given(this.constraintResolver.resolveForProperty("foo", Constrained.class)) .willReturn(Collections.emptyList()); assertThat(this.constraintDescriptions.descriptionsForProperty("foo").size()).isEqualTo(0); diff --git a/spring-restdocs-core/src/test/java/org/springframework/restdocs/constraints/ResourceBundleConstraintDescriptionResolverTests.java b/spring-restdocs-core/src/test/java/org/springframework/restdocs/constraints/ResourceBundleConstraintDescriptionResolverTests.java index 58d64d8f..c386cbbf 100644 --- a/spring-restdocs-core/src/test/java/org/springframework/restdocs/constraints/ResourceBundleConstraintDescriptionResolverTests.java +++ b/spring-restdocs-core/src/test/java/org/springframework/restdocs/constraints/ResourceBundleConstraintDescriptionResolverTests.java @@ -21,9 +21,11 @@ import java.net.URL; import java.util.Collections; import java.util.Date; +import java.util.HashSet; import java.util.List; import java.util.ListResourceBundle; import java.util.ResourceBundle; +import java.util.Set; import javax.money.MonetaryAmount; @@ -58,10 +60,14 @@ import org.hibernate.validator.constraints.Mod10Check; import org.hibernate.validator.constraints.Mod11Check; import org.hibernate.validator.constraints.Range; -import org.junit.Test; +import org.junit.jupiter.api.Test; import org.springframework.core.annotation.AnnotationUtils; +import org.springframework.core.io.ClassPathResource; +import org.springframework.core.io.Resource; +import org.springframework.core.io.support.PathMatchingResourcePatternResolver; import org.springframework.util.Assert; +import org.springframework.util.ClassUtils; import org.springframework.util.ReflectionUtils; import static org.assertj.core.api.Assertions.assertThat; @@ -71,194 +77,183 @@ * * @author Andy Wilkinson */ -public class ResourceBundleConstraintDescriptionResolverTests { +class ResourceBundleConstraintDescriptionResolverTests { private final ResourceBundleConstraintDescriptionResolver resolver = new ResourceBundleConstraintDescriptionResolver(); @Test - public void defaultMessageAssertFalse() { + void defaultMessageAssertFalse() { assertThat(constraintDescriptionForField("assertFalse")).isEqualTo("Must be false"); } @Test - public void defaultMessageAssertTrue() { + void defaultMessageAssertTrue() { assertThat(constraintDescriptionForField("assertTrue")).isEqualTo("Must be true"); } @Test - public void defaultMessageCodePointLength() { + void defaultMessageCodePointLength() { assertThat(constraintDescriptionForField("codePointLength")) .isEqualTo("Code point length must be between 2 and 5 inclusive"); } @Test - public void defaultMessageCurrency() { + void defaultMessageCurrency() { assertThat(constraintDescriptionForField("currency")) .isEqualTo("Must be in an accepted currency unit (GBP, USD)"); } @Test - public void defaultMessageDecimalMax() { + void defaultMessageDecimalMax() { assertThat(constraintDescriptionForField("decimalMax")).isEqualTo("Must be at most 9.875"); } @Test - public void defaultMessageDecimalMin() { + void defaultMessageDecimalMin() { assertThat(constraintDescriptionForField("decimalMin")).isEqualTo("Must be at least 1.5"); } @Test - public void defaultMessageDigits() { + void defaultMessageDigits() { assertThat(constraintDescriptionForField("digits")) .isEqualTo("Must have at most 2 integral digits and 5 fractional digits"); } @Test - public void defaultMessageFuture() { + void defaultMessageFuture() { assertThat(constraintDescriptionForField("future")).isEqualTo("Must be in the future"); } @Test - public void defaultMessageFutureOrPresent() { + void defaultMessageFutureOrPresent() { assertThat(constraintDescriptionForField("futureOrPresent")).isEqualTo("Must be in the future or the present"); } @Test - public void defaultMessageMax() { + void defaultMessageMax() { assertThat(constraintDescriptionForField("max")).isEqualTo("Must be at most 10"); } @Test - public void defaultMessageMin() { + void defaultMessageMin() { assertThat(constraintDescriptionForField("min")).isEqualTo("Must be at least 10"); } @Test - public void defaultMessageNotNull() { + void defaultMessageNotNull() { assertThat(constraintDescriptionForField("notNull")).isEqualTo("Must not be null"); } @Test - public void defaultMessageNull() { + void defaultMessageNull() { assertThat(constraintDescriptionForField("nul")).isEqualTo("Must be null"); } @Test - public void defaultMessagePast() { + void defaultMessagePast() { assertThat(constraintDescriptionForField("past")).isEqualTo("Must be in the past"); } @Test - public void defaultMessagePastOrPresent() { + void defaultMessagePastOrPresent() { assertThat(constraintDescriptionForField("pastOrPresent")).isEqualTo("Must be in the past or the present"); } @Test - public void defaultMessagePattern() { + void defaultMessagePattern() { assertThat(constraintDescriptionForField("pattern")) .isEqualTo("Must match the regular expression `[A-Z][a-z]+`"); } @Test - public void defaultMessageSize() { + void defaultMessageSize() { assertThat(constraintDescriptionForField("size")).isEqualTo("Size must be between 2 and 10 inclusive"); } @Test - public void defaultMessageCreditCardNumber() { + void defaultMessageCreditCardNumber() { assertThat(constraintDescriptionForField("creditCardNumber")) .isEqualTo("Must be a well-formed credit card number"); } @Test - public void defaultMessageEan() { + void defaultMessageEan() { assertThat(constraintDescriptionForField("ean")).isEqualTo("Must be a well-formed EAN13 number"); } @Test - public void defaultMessageEmail() { + void defaultMessageEmail() { assertThat(constraintDescriptionForField("email")).isEqualTo("Must be a well-formed email address"); } @Test - public void defaultMessageEmailHibernateValidator() { - assertThat(constraintDescriptionForField("emailHibernateValidator")) - .isEqualTo("Must be a well-formed email address"); - } - - @Test - public void defaultMessageLength() { + void defaultMessageLength() { assertThat(constraintDescriptionForField("length")).isEqualTo("Length must be between 2 and 10 inclusive"); } @Test - public void defaultMessageLuhnCheck() { + void defaultMessageLuhnCheck() { assertThat(constraintDescriptionForField("luhnCheck")) .isEqualTo("Must pass the Luhn Modulo 10 checksum algorithm"); } @Test - public void defaultMessageMod10Check() { + void defaultMessageMod10Check() { assertThat(constraintDescriptionForField("mod10Check")).isEqualTo("Must pass the Mod10 checksum algorithm"); } @Test - public void defaultMessageMod11Check() { + void defaultMessageMod11Check() { assertThat(constraintDescriptionForField("mod11Check")).isEqualTo("Must pass the Mod11 checksum algorithm"); } @Test - public void defaultMessageNegative() { + void defaultMessageNegative() { assertThat(constraintDescriptionForField("negative")).isEqualTo("Must be negative"); } @Test - public void defaultMessageNegativeOrZero() { + void defaultMessageNegativeOrZero() { assertThat(constraintDescriptionForField("negativeOrZero")).isEqualTo("Must be negative or zero"); } @Test - public void defaultMessageNotBlank() { + void defaultMessageNotBlank() { assertThat(constraintDescriptionForField("notBlank")).isEqualTo("Must not be blank"); } @Test - public void defaultMessageNotBlankHibernateValidator() { - assertThat(constraintDescriptionForField("notBlankHibernateValidator")).isEqualTo("Must not be blank"); - } - - @Test - public void defaultMessageNotEmpty() { + void defaultMessageNotEmpty() { assertThat(constraintDescriptionForField("notEmpty")).isEqualTo("Must not be empty"); } @Test - public void defaultMessageNotEmptyHibernateValidator() { + void defaultMessageNotEmptyHibernateValidator() { assertThat(constraintDescriptionForField("notEmpty")).isEqualTo("Must not be empty"); } @Test - public void defaultMessagePositive() { + void defaultMessagePositive() { assertThat(constraintDescriptionForField("positive")).isEqualTo("Must be positive"); } @Test - public void defaultMessagePositiveOrZero() { + void defaultMessagePositiveOrZero() { assertThat(constraintDescriptionForField("positiveOrZero")).isEqualTo("Must be positive or zero"); } @Test - public void defaultMessageRange() { + void defaultMessageRange() { assertThat(constraintDescriptionForField("range")).isEqualTo("Must be at least 10 and at most 100"); } @Test - public void defaultMessageUrl() { + void defaultMessageUrl() { assertThat(constraintDescriptionForField("url")).isEqualTo("Must be a well-formed URL"); } @Test - public void customMessage() { + void customMessage() { Thread.currentThread().setContextClassLoader(new ClassLoader() { @Override @@ -284,7 +279,7 @@ public URL getResource(String name) { } @Test - public void customResourceBundle() { + void customResourceBundle() { ResourceBundle bundle = new ListResourceBundle() { @Override @@ -298,6 +293,29 @@ protected Object[][] getContents() { assertThat(description).isEqualTo("Not null"); } + @Test + void allBeanValidationConstraintsAreTested() throws Exception { + PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(); + Resource[] resources = resolver.getResources("jakarta/validation/constraints/*.class"); + Set> beanValidationConstraints = new HashSet<>(); + for (Resource resource : resources) { + String className = ClassUtils.convertResourcePathToClassName(((ClassPathResource) resource).getPath()); + if (className.endsWith(".class")) { + className = className.substring(0, className.length() - 6); + } + Class type = Class.forName(className); + if (type.isAnnotation() && type.isAnnotationPresent(jakarta.validation.Constraint.class)) { + beanValidationConstraints.add(type); + } + } + ReflectionUtils.doWithFields(Constrained.class, (field) -> { + for (Annotation annotation : field.getAnnotations()) { + beanValidationConstraints.remove(annotation.annotationType()); + } + }); + assertThat(beanValidationConstraints).isEmpty(); + } + private String constraintDescriptionForField(String name) { return this.resolver.resolveDescription(getConstraintFromField(name)); } @@ -372,10 +390,6 @@ private static final class Constrained { @Email private String email; - @SuppressWarnings("deprecation") - @org.hibernate.validator.constraints.Email - private String emailHibernateValidator; - @Length(min = 2, max = 10) private String length; @@ -397,17 +411,9 @@ private static final class Constrained { @NotBlank private String notBlank; - @SuppressWarnings("deprecation") - @org.hibernate.validator.constraints.NotBlank - private String notBlankHibernateValidator; - @NotEmpty private String notEmpty; - @SuppressWarnings("deprecation") - @org.hibernate.validator.constraints.NotEmpty - private String notEmptyHibernateValidator; - @Positive private int positive; diff --git a/spring-restdocs-core/src/test/java/org/springframework/restdocs/constraints/ValidatorConstraintResolverTests.java b/spring-restdocs-core/src/test/java/org/springframework/restdocs/constraints/ValidatorConstraintResolverTests.java index 4dbc54f0..e5ad3f53 100644 --- a/spring-restdocs-core/src/test/java/org/springframework/restdocs/constraints/ValidatorConstraintResolverTests.java +++ b/spring-restdocs-core/src/test/java/org/springframework/restdocs/constraints/ValidatorConstraintResolverTests.java @@ -35,7 +35,7 @@ import org.assertj.core.description.TextDescription; import org.hibernate.validator.constraints.CompositionType; import org.hibernate.validator.constraints.ConstraintComposition; -import org.junit.Test; +import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThat; @@ -44,19 +44,19 @@ * * @author Andy Wilkinson */ -public class ValidatorConstraintResolverTests { +class ValidatorConstraintResolverTests { private final ValidatorConstraintResolver resolver = new ValidatorConstraintResolver(); @Test - public void singleFieldConstraint() { + void singleFieldConstraint() { List constraints = this.resolver.resolveForProperty("single", ConstrainedFields.class); assertThat(constraints).hasSize(1); assertThat(constraints.get(0).getName()).isEqualTo(NotNull.class.getName()); } @Test - public void multipleFieldConstraints() { + void multipleFieldConstraints() { List constraints = this.resolver.resolveForProperty("multiple", ConstrainedFields.class); assertThat(constraints).hasSize(2); assertThat(constraints.get(0)).is(constraint(NotNull.class)); @@ -64,13 +64,13 @@ public void multipleFieldConstraints() { } @Test - public void noFieldConstraints() { + void noFieldConstraints() { List constraints = this.resolver.resolveForProperty("none", ConstrainedFields.class); assertThat(constraints).hasSize(0); } @Test - public void compositeConstraint() { + void compositeConstraint() { List constraints = this.resolver.resolveForProperty("composite", ConstrainedFields.class); assertThat(constraints).hasSize(1); } diff --git a/spring-restdocs-core/src/test/java/org/springframework/restdocs/cookies/RequestCookiesSnippetFailureTests.java b/spring-restdocs-core/src/test/java/org/springframework/restdocs/cookies/RequestCookiesSnippetFailureTests.java deleted file mode 100644 index 01ac8533..00000000 --- a/spring-restdocs-core/src/test/java/org/springframework/restdocs/cookies/RequestCookiesSnippetFailureTests.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright 2014-present 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]"); - } - -} diff --git a/spring-restdocs-core/src/test/java/org/springframework/restdocs/cookies/RequestCookiesSnippetTests.java b/spring-restdocs-core/src/test/java/org/springframework/restdocs/cookies/RequestCookiesSnippetTests.java index 6d115a05..4c1ba194 100644 --- a/spring-restdocs-core/src/test/java/org/springframework/restdocs/cookies/RequestCookiesSnippetTests.java +++ b/spring-restdocs-core/src/test/java/org/springframework/restdocs/cookies/RequestCookiesSnippetTests.java @@ -20,18 +20,15 @@ import java.util.Arrays; import java.util.Collections; -import org.junit.Test; - -import org.springframework.restdocs.AbstractSnippetTests; -import org.springframework.restdocs.templates.TemplateEngine; -import org.springframework.restdocs.templates.TemplateFormat; -import org.springframework.restdocs.templates.TemplateFormats; -import org.springframework.restdocs.templates.TemplateResourceResolver; -import org.springframework.restdocs.templates.mustache.MustacheTemplateEngine; +import org.springframework.restdocs.snippet.SnippetException; +import org.springframework.restdocs.testfixtures.jupiter.AssertableSnippets; +import org.springframework.restdocs.testfixtures.jupiter.OperationBuilder; +import org.springframework.restdocs.testfixtures.jupiter.RenderedSnippetTest; +import org.springframework.restdocs.testfixtures.jupiter.SnippetTemplate; +import org.springframework.restdocs.testfixtures.jupiter.SnippetTest; import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.BDDMockito.given; -import static org.mockito.Mockito.mock; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.springframework.restdocs.cookies.CookieDocumentation.cookieWithName; import static org.springframework.restdocs.snippet.Attributes.attributes; import static org.springframework.restdocs.snippet.Attributes.key; @@ -42,138 +39,141 @@ * @author Clyde Stubbs * @author Andy Wilkinson */ -public class RequestCookiesSnippetTests extends AbstractSnippetTests { - - public RequestCookiesSnippetTests(String name, TemplateFormat templateFormat) { - super(name, templateFormat); - } +class RequestCookiesSnippetTests { - @Test - public void requestWithCookies() throws IOException { + @RenderedSnippetTest + void requestWithCookies(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException { new RequestCookiesSnippet( Arrays.asList(cookieWithName("tz").description("one"), cookieWithName("logged_in").description("two"))) - .document(this.operationBuilder.request("http://localhost") + .document(operationBuilder.request("http://localhost") .cookie("tz", "Europe%2FLondon") .cookie("logged_in", "true") .build()); - assertThat(this.generatedSnippets.requestCookies()) - .is(tableWithHeader("Name", "Description").row("`tz`", "one").row("`logged_in`", "two")); + assertThat(snippets.requestCookies()) + .isTable((table) -> table.withHeader("Name", "Description").row("`tz`", "one").row("`logged_in`", "two")); } - @Test - public void ignoredRequestCookie() throws IOException { + @RenderedSnippetTest + void ignoredRequestCookie(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException { new RequestCookiesSnippet( Arrays.asList(cookieWithName("tz").ignored(), cookieWithName("logged_in").description("two"))) - .document(this.operationBuilder.request("http://localhost") + .document(operationBuilder.request("http://localhost") .cookie("tz", "Europe%2FLondon") .cookie("logged_in", "true") .build()); - assertThat(this.generatedSnippets.requestCookies()) - .is(tableWithHeader("Name", "Description").row("`logged_in`", "two")); + assertThat(snippets.requestCookies()) + .isTable((table) -> table.withHeader("Name", "Description").row("`logged_in`", "two")); } - @Test - public void allUndocumentedCookiesCanBeIgnored() throws IOException { + @RenderedSnippetTest + void allUndocumentedCookiesCanBeIgnored(OperationBuilder operationBuilder, AssertableSnippets snippets) + throws IOException { new RequestCookiesSnippet( Arrays.asList(cookieWithName("tz").description("one"), cookieWithName("logged_in").description("two")), true) - .document(this.operationBuilder.request("http://localhost") + .document(operationBuilder.request("http://localhost") .cookie("tz", "Europe%2FLondon") .cookie("logged_in", "true") .cookie("user_session", "abcd1234efgh5678") .build()); - assertThat(this.generatedSnippets.requestCookies()) - .is(tableWithHeader("Name", "Description").row("`tz`", "one").row("`logged_in`", "two")); + assertThat(snippets.requestCookies()) + .isTable((table) -> table.withHeader("Name", "Description").row("`tz`", "one").row("`logged_in`", "two")); } - @Test - public void missingOptionalCookie() throws IOException { + @RenderedSnippetTest + void missingOptionalCookie(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException { new RequestCookiesSnippet(Arrays.asList(cookieWithName("tz").description("one").optional(), cookieWithName("logged_in").description("two"))) - .document(this.operationBuilder.request("http://localhost").cookie("logged_in", "true").build()); - assertThat(this.generatedSnippets.requestCookies()) - .is(tableWithHeader("Name", "Description").row("`tz`", "one").row("`logged_in`", "two")); + .document(operationBuilder.request("http://localhost").cookie("logged_in", "true").build()); + assertThat(snippets.requestCookies()) + .isTable((table) -> table.withHeader("Name", "Description").row("`tz`", "one").row("`logged_in`", "two")); } - @Test - public void requestCookiesWithCustomAttributes() throws IOException { - TemplateResourceResolver resolver = mock(TemplateResourceResolver.class); - given(resolver.resolveTemplateResource("request-cookies")) - .willReturn(snippetResource("request-cookies-with-title")); + @RenderedSnippetTest + @SnippetTemplate(snippet = "request-cookies", template = "request-cookies-with-title") + void requestCookiesWithCustomAttributes(OperationBuilder operationBuilder, AssertableSnippets snippets) + throws IOException { new RequestCookiesSnippet(Collections.singletonList(cookieWithName("tz").description("one")), attributes(key("title").value("Custom title"))) - .document(this.operationBuilder - .attribute(TemplateEngine.class.getName(), new MustacheTemplateEngine(resolver)) - .request("http://localhost") - .cookie("tz", "Europe%2FLondon") - .build()); - assertThat(this.generatedSnippets.requestCookies()).contains("Custom title"); + .document(operationBuilder.request("http://localhost").cookie("tz", "Europe%2FLondon").build()); + assertThat(snippets.requestCookies()).contains("Custom title"); } - @Test - public void requestCookiesWithCustomDescriptorAttributes() throws IOException { - TemplateResourceResolver resolver = mock(TemplateResourceResolver.class); - given(resolver.resolveTemplateResource("request-cookies")) - .willReturn(snippetResource("request-cookies-with-extra-column")); + @RenderedSnippetTest + @SnippetTemplate(snippet = "request-cookies", template = "request-cookies-with-extra-column") + void requestCookiesWithCustomDescriptorAttributes(OperationBuilder operationBuilder, AssertableSnippets snippets) + throws IOException { new RequestCookiesSnippet( Arrays.asList(cookieWithName("tz").description("one").attributes(key("foo").value("alpha")), cookieWithName("logged_in").description("two").attributes(key("foo").value("bravo")))) - .document(this.operationBuilder - .attribute(TemplateEngine.class.getName(), new MustacheTemplateEngine(resolver)) - .request("http://localhost") + .document(operationBuilder.request("http://localhost") .cookie("tz", "Europe%2FLondon") .cookie("logged_in", "true") .build()); - assertThat(this.generatedSnippets.requestCookies()).is(// - tableWithHeader("Name", "Description", "Foo").row("tz", "one", "alpha") - .row("logged_in", "two", "bravo")); + assertThat(snippets.requestCookies()).isTable((table) -> table.withHeader("Name", "Description", "Foo") + .row("tz", "one", "alpha") + .row("logged_in", "two", "bravo")); } - @Test - public void additionalDescriptors() throws IOException { + @RenderedSnippetTest + void additionalDescriptors(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException { new RequestCookiesSnippet( Arrays.asList(cookieWithName("tz").description("one"), cookieWithName("logged_in").description("two"))) .and(cookieWithName("user_session").description("three")) - .document(this.operationBuilder.request("http://localhost") + .document(operationBuilder.request("http://localhost") .cookie("tz", "Europe%2FLondon") .cookie("logged_in", "true") .cookie("user_session", "abcd1234efgh5678") .build()); - assertThat(this.generatedSnippets.requestCookies()).is(tableWithHeader("Name", "Description").row("`tz`", "one") + assertThat(snippets.requestCookies()).isTable((table) -> table.withHeader("Name", "Description") + .row("`tz`", "one") .row("`logged_in`", "two") .row("`user_session`", "three")); } - @Test - public void additionalDescriptorsWithRelaxedRequestCookies() throws IOException { + @RenderedSnippetTest + void additionalDescriptorsWithRelaxedRequestCookies(OperationBuilder operationBuilder, AssertableSnippets snippets) + throws IOException { new RequestCookiesSnippet( Arrays.asList(cookieWithName("tz").description("one"), cookieWithName("logged_in").description("two")), true) .and(cookieWithName("user_session").description("three")) - .document(this.operationBuilder.request("http://localhost") + .document(operationBuilder.request("http://localhost") .cookie("tz", "Europe%2FLondon") .cookie("logged_in", "true") .cookie("user_session", "abcd1234efgh5678") .cookie("color_theme", "light") .build()); - assertThat(this.generatedSnippets.requestCookies()).is(tableWithHeader("Name", "Description").row("`tz`", "one") + assertThat(snippets.requestCookies()).isTable((table) -> table.withHeader("Name", "Description") + .row("`tz`", "one") .row("`logged_in`", "two") .row("`user_session`", "three")); } - @Test - public void tableCellContentIsEscapedWhenNecessary() throws IOException { + @RenderedSnippetTest + void tableCellContentIsEscapedWhenNecessary(OperationBuilder operationBuilder, AssertableSnippets snippets) + throws IOException { new RequestCookiesSnippet(Collections.singletonList(cookieWithName("Foo|Bar").description("one|two"))) - .document(this.operationBuilder.request("http://localhost").cookie("Foo|Bar", "baz").build()); - assertThat(this.generatedSnippets.requestCookies()).is(tableWithHeader("Name", "Description") - .row(escapeIfNecessary("`Foo|Bar`"), escapeIfNecessary("one|two"))); + .document(operationBuilder.request("http://localhost").cookie("Foo|Bar", "baz").build()); + assertThat(snippets.requestCookies()) + .isTable((table) -> table.withHeader("Name", "Description").row("`Foo|Bar`", "one|two")); + } + + @SnippetTest + void missingRequestCookie(OperationBuilder operationBuilder) { + assertThatExceptionOfType(SnippetException.class) + .isThrownBy(() -> new RequestCookiesSnippet( + Collections.singletonList(CookieDocumentation.cookieWithName("JSESSIONID").description("one"))) + .document(operationBuilder.request("http://localhost").build())) + .withMessage("Cookies with the following names were not found in the request: [JSESSIONID]"); } - private String escapeIfNecessary(String input) { - if (this.templateFormat.getId().equals(TemplateFormats.markdown().getId())) { - return input; - } - return input.replace("|", "\\|"); + @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]"); } } diff --git a/spring-restdocs-core/src/test/java/org/springframework/restdocs/cookies/ResponseCookiesSnippetFailureTests.java b/spring-restdocs-core/src/test/java/org/springframework/restdocs/cookies/ResponseCookiesSnippetFailureTests.java deleted file mode 100644 index 49c8f2ce..00000000 --- a/spring-restdocs-core/src/test/java/org/springframework/restdocs/cookies/ResponseCookiesSnippetFailureTests.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright 2014-present 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]"); - } - -} diff --git a/spring-restdocs-core/src/test/java/org/springframework/restdocs/cookies/ResponseCookiesSnippetTests.java b/spring-restdocs-core/src/test/java/org/springframework/restdocs/cookies/ResponseCookiesSnippetTests.java index d539660a..dbf9533a 100644 --- a/spring-restdocs-core/src/test/java/org/springframework/restdocs/cookies/ResponseCookiesSnippetTests.java +++ b/spring-restdocs-core/src/test/java/org/springframework/restdocs/cookies/ResponseCookiesSnippetTests.java @@ -20,18 +20,12 @@ import java.util.Arrays; import java.util.Collections; -import org.junit.Test; - -import org.springframework.restdocs.AbstractSnippetTests; -import org.springframework.restdocs.templates.TemplateEngine; -import org.springframework.restdocs.templates.TemplateFormat; -import org.springframework.restdocs.templates.TemplateFormats; -import org.springframework.restdocs.templates.TemplateResourceResolver; -import org.springframework.restdocs.templates.mustache.MustacheTemplateEngine; +import org.springframework.restdocs.testfixtures.jupiter.AssertableSnippets; +import org.springframework.restdocs.testfixtures.jupiter.OperationBuilder; +import org.springframework.restdocs.testfixtures.jupiter.RenderedSnippetTest; +import org.springframework.restdocs.testfixtures.jupiter.SnippetTemplate; import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.BDDMockito.given; -import static org.mockito.Mockito.mock; import static org.springframework.restdocs.cookies.CookieDocumentation.cookieWithName; import static org.springframework.restdocs.snippet.Attributes.attributes; import static org.springframework.restdocs.snippet.Attributes.key; @@ -42,141 +36,127 @@ * @author Clyde Stubbs * @author Andy Wilkinson */ -public class ResponseCookiesSnippetTests extends AbstractSnippetTests { - - public ResponseCookiesSnippetTests(String name, TemplateFormat templateFormat) { - super(name, templateFormat); - } +class ResponseCookiesSnippetTests { - @Test - public void responseWithCookies() throws IOException { + @RenderedSnippetTest + void responseWithCookies(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException { new ResponseCookiesSnippet(Arrays.asList(cookieWithName("has_recent_activity").description("one"), cookieWithName("user_session").description("two"))) - .document(this.operationBuilder.response() + .document(operationBuilder.response() .cookie("has_recent_activity", "true") .cookie("user_session", "1234abcd5678efgh") .build()); - assertThat(this.generatedSnippets.responseCookies()) - .is(tableWithHeader("Name", "Description").row("`has_recent_activity`", "one") - .row("`user_session`", "two")); + assertThat(snippets.responseCookies()).isTable((table) -> table.withHeader("Name", "Description") + .row("`has_recent_activity`", "one") + .row("`user_session`", "two")); } - @Test - public void ignoredResponseCookie() throws IOException { + @RenderedSnippetTest + void ignoredResponseCookie(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException { new ResponseCookiesSnippet(Arrays.asList(cookieWithName("has_recent_activity").ignored(), cookieWithName("user_session").description("two"))) - .document(this.operationBuilder.response() + .document(operationBuilder.response() .cookie("has_recent_activity", "true") .cookie("user_session", "1234abcd5678efgh") .build()); - assertThat(this.generatedSnippets.responseCookies()) - .is(tableWithHeader("Name", "Description").row("`user_session`", "two")); + assertThat(snippets.responseCookies()) + .isTable((table) -> table.withHeader("Name", "Description").row("`user_session`", "two")); } - @Test - public void allUndocumentedResponseCookiesCanBeIgnored() throws IOException { + @RenderedSnippetTest + void allUndocumentedResponseCookiesCanBeIgnored(OperationBuilder operationBuilder, AssertableSnippets snippets) + throws IOException { new ResponseCookiesSnippet(Arrays.asList(cookieWithName("has_recent_activity").description("one"), cookieWithName("user_session").description("two")), true) - .document(this.operationBuilder.response() + .document(operationBuilder.response() .cookie("has_recent_activity", "true") .cookie("user_session", "1234abcd5678efgh") .cookie("some_cookie", "value") .build()); - assertThat(this.generatedSnippets.responseCookies()) - .is(tableWithHeader("Name", "Description").row("`has_recent_activity`", "one") - .row("`user_session`", "two")); + assertThat(snippets.responseCookies()).isTable((table) -> table.withHeader("Name", "Description") + .row("`has_recent_activity`", "one") + .row("`user_session`", "two")); } - @Test - public void missingOptionalResponseCookie() throws IOException { + @RenderedSnippetTest + void missingOptionalResponseCookie(OperationBuilder operationBuilder, AssertableSnippets snippets) + throws IOException { new ResponseCookiesSnippet(Arrays.asList(cookieWithName("has_recent_activity").description("one").optional(), cookieWithName("user_session").description("two"))) - .document(this.operationBuilder.response().cookie("user_session", "1234abcd5678efgh").build()); - assertThat(this.generatedSnippets.responseCookies()) - .is(tableWithHeader("Name", "Description").row("`has_recent_activity`", "one") - .row("`user_session`", "two")); + .document(operationBuilder.response().cookie("user_session", "1234abcd5678efgh").build()); + assertThat(snippets.responseCookies()).isTable((table) -> table.withHeader("Name", "Description") + .row("`has_recent_activity`", "one") + .row("`user_session`", "two")); } - @Test - public void responseCookiesWithCustomAttributes() throws IOException { - TemplateResourceResolver resolver = mock(TemplateResourceResolver.class); - given(resolver.resolveTemplateResource("response-cookies")) - .willReturn(snippetResource("response-cookies-with-title")); + @RenderedSnippetTest + @SnippetTemplate(snippet = "response-cookies", template = "response-cookies-with-title") + void responseCookiesWithCustomAttributes(OperationBuilder operationBuilder, AssertableSnippets snippets) + throws IOException { new ResponseCookiesSnippet(Collections.singletonList(cookieWithName("has_recent_activity").description("one")), attributes(key("title").value("Custom title"))) - .document(this.operationBuilder - .attribute(TemplateEngine.class.getName(), new MustacheTemplateEngine(resolver)) - .response() - .cookie("has_recent_activity", "true") - .build()); - assertThat(this.generatedSnippets.responseCookies()).contains("Custom title"); + .document(operationBuilder.response().cookie("has_recent_activity", "true").build()); + assertThat(snippets.responseCookies()).contains("Custom title"); } - @Test - public void responseCookiesWithCustomDescriptorAttributes() throws IOException { - TemplateResourceResolver resolver = mock(TemplateResourceResolver.class); - given(resolver.resolveTemplateResource("response-cookies")) - .willReturn(snippetResource("response-cookies-with-extra-column")); + @RenderedSnippetTest + @SnippetTemplate(snippet = "response-cookies", template = "response-cookies-with-extra-column") + void responseCookiesWithCustomDescriptorAttributes(OperationBuilder operationBuilder, AssertableSnippets snippets) + throws IOException { new ResponseCookiesSnippet(Arrays.asList( cookieWithName("has_recent_activity").description("one").attributes(key("foo").value("alpha")), cookieWithName("user_session").description("two").attributes(key("foo").value("bravo")), cookieWithName("color_theme").description("three").attributes(key("foo").value("charlie")))) - .document(this.operationBuilder - .attribute(TemplateEngine.class.getName(), new MustacheTemplateEngine(resolver)) - .response() + .document(operationBuilder.response() .cookie("has_recent_activity", "true") .cookie("user_session", "1234abcd5678efgh") .cookie("color_theme", "high_contrast") .build()); - assertThat(this.generatedSnippets.responseCookies()) - .is(tableWithHeader("Name", "Description", "Foo").row("has_recent_activity", "one", "alpha") - .row("user_session", "two", "bravo") - .row("color_theme", "three", "charlie")); + assertThat(snippets.responseCookies()).isTable((table) -> table.withHeader("Name", "Description", "Foo") + .row("has_recent_activity", "one", "alpha") + .row("user_session", "two", "bravo") + .row("color_theme", "three", "charlie")); } - @Test - public void additionalDescriptors() throws IOException { + @RenderedSnippetTest + void additionalDescriptors(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException { CookieDocumentation .responseCookies(cookieWithName("has_recent_activity").description("one"), cookieWithName("user_session").description("two")) .and(cookieWithName("color_theme").description("three")) - .document(this.operationBuilder.response() + .document(operationBuilder.response() .cookie("has_recent_activity", "true") .cookie("user_session", "1234abcd5678efgh") .cookie("color_theme", "light") .build()); - assertThat(this.generatedSnippets.responseCookies()) - .is(tableWithHeader("Name", "Description").row("`has_recent_activity`", "one") - .row("`user_session`", "two") - .row("`color_theme`", "three")); + assertThat(snippets.responseCookies()).isTable((table) -> table.withHeader("Name", "Description") + .row("`has_recent_activity`", "one") + .row("`user_session`", "two") + .row("`color_theme`", "three")); } - @Test - public void additionalDescriptorsWithRelaxedResponseCookies() throws IOException { + @RenderedSnippetTest + void additionalDescriptorsWithRelaxedResponseCookies(OperationBuilder operationBuilder, AssertableSnippets snippets) + throws IOException { CookieDocumentation.relaxedResponseCookies(cookieWithName("has_recent_activity").description("one")) .and(cookieWithName("color_theme").description("two")) - .document(this.operationBuilder.response() + .document(operationBuilder.response() .cookie("has_recent_activity", "true") .cookie("user_session", "1234abcd5678efgh") .cookie("color_theme", "light") .build()); - assertThat(this.generatedSnippets.responseCookies()) - .is(tableWithHeader("Name", "Description").row("`has_recent_activity`", "one").row("`color_theme`", "two")); + assertThat(snippets.responseCookies()).isTable((table) -> table.withHeader("Name", "Description") + .row("`has_recent_activity`", "one") + .row("`color_theme`", "two")); } - @Test - public void tableCellContentIsEscapedWhenNecessary() throws IOException { + @RenderedSnippetTest + void tableCellContentIsEscapedWhenNecessary(OperationBuilder operationBuilder, AssertableSnippets snippets) + throws IOException { new ResponseCookiesSnippet(Collections.singletonList(cookieWithName("Foo|Bar").description("one|two"))) - .document(this.operationBuilder.response().cookie("Foo|Bar", "baz").build()); - assertThat(this.generatedSnippets.responseCookies()).is(tableWithHeader("Name", "Description") - .row(escapeIfNecessary("`Foo|Bar`"), escapeIfNecessary("one|two"))); - } - - private String escapeIfNecessary(String input) { - if (this.templateFormat.getId().equals(TemplateFormats.markdown().getId())) { - return input; - } - return input.replace("|", "\\|"); + .document(operationBuilder.response().cookie("Foo|Bar", "baz").build()); + assertThat(snippets.responseCookies()) + .isTable((table) -> table.withHeader("Name", "Description").row("`Foo|Bar`", "one|two")); } } diff --git a/spring-restdocs-core/src/test/java/org/springframework/restdocs/headers/RequestHeadersSnippetFailureTests.java b/spring-restdocs-core/src/test/java/org/springframework/restdocs/headers/RequestHeadersSnippetFailureTests.java deleted file mode 100644 index 22509a60..00000000 --- a/spring-restdocs-core/src/test/java/org/springframework/restdocs/headers/RequestHeadersSnippetFailureTests.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright 2014-present 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]"); - - } - -} diff --git a/spring-restdocs-core/src/test/java/org/springframework/restdocs/headers/RequestHeadersSnippetTests.java b/spring-restdocs-core/src/test/java/org/springframework/restdocs/headers/RequestHeadersSnippetTests.java index 05e424af..58f6b40a 100644 --- a/spring-restdocs-core/src/test/java/org/springframework/restdocs/headers/RequestHeadersSnippetTests.java +++ b/spring-restdocs-core/src/test/java/org/springframework/restdocs/headers/RequestHeadersSnippetTests.java @@ -19,18 +19,15 @@ import java.io.IOException; import java.util.Arrays; -import org.junit.Test; - -import org.springframework.restdocs.AbstractSnippetTests; -import org.springframework.restdocs.templates.TemplateEngine; -import org.springframework.restdocs.templates.TemplateFormat; -import org.springframework.restdocs.templates.TemplateFormats; -import org.springframework.restdocs.templates.TemplateResourceResolver; -import org.springframework.restdocs.templates.mustache.MustacheTemplateEngine; +import org.springframework.restdocs.snippet.SnippetException; +import org.springframework.restdocs.testfixtures.jupiter.AssertableSnippets; +import org.springframework.restdocs.testfixtures.jupiter.OperationBuilder; +import org.springframework.restdocs.testfixtures.jupiter.RenderedSnippetTest; +import org.springframework.restdocs.testfixtures.jupiter.SnippetTemplate; +import org.springframework.restdocs.testfixtures.jupiter.SnippetTest; import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.BDDMockito.given; -import static org.mockito.Mockito.mock; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.springframework.restdocs.headers.HeaderDocumentation.headerWithName; import static org.springframework.restdocs.snippet.Attributes.attributes; import static org.springframework.restdocs.snippet.Attributes.key; @@ -41,19 +38,15 @@ * @author Andreas Evers * @author Andy Wilkinson */ -public class RequestHeadersSnippetTests extends AbstractSnippetTests { - - public RequestHeadersSnippetTests(String name, TemplateFormat templateFormat) { - super(name, templateFormat); - } +class RequestHeadersSnippetTests { - @Test - public void requestWithHeaders() throws IOException { + @RenderedSnippetTest + void requestWithHeaders(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException { new RequestHeadersSnippet(Arrays.asList(headerWithName("X-Test").description("one"), headerWithName("Accept").description("two"), headerWithName("Accept-Encoding").description("three"), headerWithName("Accept-Language").description("four"), headerWithName("Cache-Control").description("five"), headerWithName("Connection").description("six"))) - .document(this.operationBuilder.request("http://localhost") + .document(operationBuilder.request("http://localhost") .header("X-Test", "test") .header("Accept", "*/*") .header("Accept-Encoding", "gzip, deflate") @@ -61,79 +54,69 @@ public void requestWithHeaders() throws IOException { .header("Cache-Control", "max-age=0") .header("Connection", "keep-alive") .build()); - assertThat(this.generatedSnippets.requestHeaders()) - .is(tableWithHeader("Name", "Description").row("`X-Test`", "one") - .row("`Accept`", "two") - .row("`Accept-Encoding`", "three") - .row("`Accept-Language`", "four") - .row("`Cache-Control`", "five") - .row("`Connection`", "six")); + assertThat(snippets.requestHeaders()).isTable((table) -> table.withHeader("Name", "Description") + .row("`X-Test`", "one") + .row("`Accept`", "two") + .row("`Accept-Encoding`", "three") + .row("`Accept-Language`", "four") + .row("`Cache-Control`", "five") + .row("`Connection`", "six")); } - @Test - public void caseInsensitiveRequestHeaders() throws IOException { + @RenderedSnippetTest + void caseInsensitiveRequestHeaders(OperationBuilder operationBuilder, AssertableSnippets snippets) + throws IOException { new RequestHeadersSnippet(Arrays.asList(headerWithName("X-Test").description("one"))) - .document(this.operationBuilder.request("/").header("X-test", "test").build()); - assertThat(this.generatedSnippets.requestHeaders()) - .is(tableWithHeader("Name", "Description").row("`X-Test`", "one")); + .document(operationBuilder.request("/").header("X-test", "test").build()); + assertThat(snippets.requestHeaders()) + .isTable((table) -> table.withHeader("Name", "Description").row("`X-Test`", "one")); } - @Test - public void undocumentedRequestHeader() throws IOException { - new RequestHeadersSnippet(Arrays.asList(headerWithName("X-Test").description("one"))) - .document(this.operationBuilder.request("http://localhost") - .header("X-Test", "test") - .header("Accept", "*/*") - .build()); - assertThat(this.generatedSnippets.requestHeaders()) - .is(tableWithHeader("Name", "Description").row("`X-Test`", "one")); + @RenderedSnippetTest + void undocumentedRequestHeader(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException { + new RequestHeadersSnippet(Arrays.asList(headerWithName("X-Test").description("one"))).document( + operationBuilder.request("http://localhost").header("X-Test", "test").header("Accept", "*/*").build()); + assertThat(snippets.requestHeaders()) + .isTable((table) -> table.withHeader("Name", "Description").row("`X-Test`", "one")); } - @Test - public void requestHeadersWithCustomAttributes() throws IOException { - TemplateResourceResolver resolver = mock(TemplateResourceResolver.class); - given(resolver.resolveTemplateResource("request-headers")) - .willReturn(snippetResource("request-headers-with-title")); + @RenderedSnippetTest + @SnippetTemplate(snippet = "request-headers", template = "request-headers-with-title") + void requestHeadersWithCustomAttributes(OperationBuilder operationBuilder, AssertableSnippets snippets) + throws IOException { new RequestHeadersSnippet(Arrays.asList(headerWithName("X-Test").description("one")), attributes(key("title").value("Custom title"))) - .document(this.operationBuilder - .attribute(TemplateEngine.class.getName(), new MustacheTemplateEngine(resolver)) - .request("http://localhost") - .header("X-Test", "test") - .build()); - assertThat(this.generatedSnippets.requestHeaders()).contains("Custom title"); + .document(operationBuilder.request("http://localhost").header("X-Test", "test").build()); + assertThat(snippets.requestHeaders()).contains("Custom title"); } - @Test - public void requestHeadersWithCustomDescriptorAttributes() throws IOException { - TemplateResourceResolver resolver = mock(TemplateResourceResolver.class); - given(resolver.resolveTemplateResource("request-headers")) - .willReturn(snippetResource("request-headers-with-extra-column")); + @RenderedSnippetTest + @SnippetTemplate(snippet = "request-headers", template = "request-headers-with-extra-column") + void requestHeadersWithCustomDescriptorAttributes(OperationBuilder operationBuilder, AssertableSnippets snippets) + throws IOException { new RequestHeadersSnippet( Arrays.asList(headerWithName("X-Test").description("one").attributes(key("foo").value("alpha")), headerWithName("Accept-Encoding").description("two").attributes(key("foo").value("bravo")), headerWithName("Accept").description("three").attributes(key("foo").value("charlie")))) - .document(this.operationBuilder - .attribute(TemplateEngine.class.getName(), new MustacheTemplateEngine(resolver)) - .request("http://localhost") + .document(operationBuilder.request("http://localhost") .header("X-Test", "test") .header("Accept-Encoding", "gzip, deflate") .header("Accept", "*/*") .build()); - assertThat(this.generatedSnippets.requestHeaders()).is(// - tableWithHeader("Name", "Description", "Foo").row("X-Test", "one", "alpha") - .row("Accept-Encoding", "two", "bravo") - .row("Accept", "three", "charlie")); + assertThat(snippets.requestHeaders()).isTable((table) -> table.withHeader("Name", "Description", "Foo") + .row("X-Test", "one", "alpha") + .row("Accept-Encoding", "two", "bravo") + .row("Accept", "three", "charlie")); } - @Test - public void additionalDescriptors() throws IOException { + @RenderedSnippetTest + void additionalDescriptors(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException { HeaderDocumentation .requestHeaders(headerWithName("X-Test").description("one"), headerWithName("Accept").description("two"), headerWithName("Accept-Encoding").description("three"), headerWithName("Accept-Language").description("four")) .and(headerWithName("Cache-Control").description("five"), headerWithName("Connection").description("six")) - .document(this.operationBuilder.request("http://localhost") + .document(operationBuilder.request("http://localhost") .header("X-Test", "test") .header("Accept", "*/*") .header("Accept-Encoding", "gzip, deflate") @@ -141,28 +124,39 @@ public void additionalDescriptors() throws IOException { .header("Cache-Control", "max-age=0") .header("Connection", "keep-alive") .build()); - assertThat(this.generatedSnippets.requestHeaders()) - .is(tableWithHeader("Name", "Description").row("`X-Test`", "one") - .row("`Accept`", "two") - .row("`Accept-Encoding`", "three") - .row("`Accept-Language`", "four") - .row("`Cache-Control`", "five") - .row("`Connection`", "six")); + assertThat(snippets.requestHeaders()).isTable((table) -> table.withHeader("Name", "Description") + .row("`X-Test`", "one") + .row("`Accept`", "two") + .row("`Accept-Encoding`", "three") + .row("`Accept-Language`", "four") + .row("`Cache-Control`", "five") + .row("`Connection`", "six")); } - @Test - public void tableCellContentIsEscapedWhenNecessary() throws IOException { + @RenderedSnippetTest + void tableCellContentIsEscapedWhenNecessary(OperationBuilder operationBuilder, AssertableSnippets snippets) + throws IOException { new RequestHeadersSnippet(Arrays.asList(headerWithName("Foo|Bar").description("one|two"))) - .document(this.operationBuilder.request("http://localhost").header("Foo|Bar", "baz").build()); - assertThat(this.generatedSnippets.requestHeaders()).is(tableWithHeader("Name", "Description") - .row(escapeIfNecessary("`Foo|Bar`"), escapeIfNecessary("one|two"))); + .document(operationBuilder.request("http://localhost").header("Foo|Bar", "baz").build()); + assertThat(snippets.requestHeaders()) + .isTable((table) -> table.withHeader("Name", "Description").row("`Foo|Bar`", "one|two")); } - private String escapeIfNecessary(String input) { - if (this.templateFormat.getId().equals(TemplateFormats.markdown().getId())) { - return input; - } - return input.replace("|", "\\|"); + @SnippetTest + void missingRequestHeader(OperationBuilder operationBuilder) { + assertThatExceptionOfType(SnippetException.class) + .isThrownBy(() -> new RequestHeadersSnippet(Arrays.asList(headerWithName("Accept").description("one"))) + .document(operationBuilder.request("http://localhost").build())) + .withMessage("Headers with the following names were not found in the request: [Accept]"); + } + + @SnippetTest + void undocumentedRequestHeaderAndMissingRequestHeader(OperationBuilder operationBuilder) { + assertThatExceptionOfType(SnippetException.class) + .isThrownBy(() -> new RequestHeadersSnippet(Arrays.asList(headerWithName("Accept").description("one"))) + .document(operationBuilder.request("http://localhost").header("X-Test", "test").build())) + .withMessageEndingWith("Headers with the following names were not found in the request: [Accept]"); + } } diff --git a/spring-restdocs-core/src/test/java/org/springframework/restdocs/headers/ResponseHeadersSnippetFailureTests.java b/spring-restdocs-core/src/test/java/org/springframework/restdocs/headers/ResponseHeadersSnippetFailureTests.java deleted file mode 100644 index 52a8cb2c..00000000 --- a/spring-restdocs-core/src/test/java/org/springframework/restdocs/headers/ResponseHeadersSnippetFailureTests.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright 2014-present 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]"); - } - -} diff --git a/spring-restdocs-core/src/test/java/org/springframework/restdocs/headers/ResponseHeadersSnippetTests.java b/spring-restdocs-core/src/test/java/org/springframework/restdocs/headers/ResponseHeadersSnippetTests.java index b8725490..e8f6721e 100644 --- a/spring-restdocs-core/src/test/java/org/springframework/restdocs/headers/ResponseHeadersSnippetTests.java +++ b/spring-restdocs-core/src/test/java/org/springframework/restdocs/headers/ResponseHeadersSnippetTests.java @@ -19,18 +19,15 @@ import java.io.IOException; import java.util.Arrays; -import org.junit.Test; - -import org.springframework.restdocs.AbstractSnippetTests; -import org.springframework.restdocs.templates.TemplateEngine; -import org.springframework.restdocs.templates.TemplateFormat; -import org.springframework.restdocs.templates.TemplateFormats; -import org.springframework.restdocs.templates.TemplateResourceResolver; -import org.springframework.restdocs.templates.mustache.MustacheTemplateEngine; +import org.springframework.restdocs.snippet.SnippetException; +import org.springframework.restdocs.testfixtures.jupiter.AssertableSnippets; +import org.springframework.restdocs.testfixtures.jupiter.OperationBuilder; +import org.springframework.restdocs.testfixtures.jupiter.RenderedSnippetTest; +import org.springframework.restdocs.testfixtures.jupiter.SnippetTemplate; +import org.springframework.restdocs.testfixtures.jupiter.SnippetTest; import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.BDDMockito.given; -import static org.mockito.Mockito.mock; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.springframework.restdocs.headers.HeaderDocumentation.headerWithName; import static org.springframework.restdocs.snippet.Attributes.attributes; import static org.springframework.restdocs.snippet.Attributes.key; @@ -41,119 +38,120 @@ * @author Andreas Evers * @author Andy Wilkinson */ -public class ResponseHeadersSnippetTests extends AbstractSnippetTests { - - public ResponseHeadersSnippetTests(String name, TemplateFormat templateFormat) { - super(name, templateFormat); - } +class ResponseHeadersSnippetTests { - @Test - public void responseWithHeaders() throws IOException { + @RenderedSnippetTest + void responseWithHeaders(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException { new ResponseHeadersSnippet(Arrays.asList(headerWithName("X-Test").description("one"), headerWithName("Content-Type").description("two"), headerWithName("Etag").description("three"), headerWithName("Cache-Control").description("five"), headerWithName("Vary").description("six"))) - .document(this.operationBuilder.response() + .document(operationBuilder.response() .header("X-Test", "test") .header("Content-Type", "application/json") .header("Etag", "lskjadldj3ii32l2ij23") .header("Cache-Control", "max-age=0") .header("Vary", "User-Agent") .build()); - assertThat(this.generatedSnippets.responseHeaders()) - .is(tableWithHeader("Name", "Description").row("`X-Test`", "one") - .row("`Content-Type`", "two") - .row("`Etag`", "three") - .row("`Cache-Control`", "five") - .row("`Vary`", "six")); + assertThat(snippets.responseHeaders()).isTable((table) -> table.withHeader("Name", "Description") + .row("`X-Test`", "one") + .row("`Content-Type`", "two") + .row("`Etag`", "three") + .row("`Cache-Control`", "five") + .row("`Vary`", "six")); } - @Test - public void caseInsensitiveResponseHeaders() throws IOException { + @RenderedSnippetTest + void caseInsensitiveResponseHeaders(OperationBuilder operationBuilder, AssertableSnippets snippets) + throws IOException { new ResponseHeadersSnippet(Arrays.asList(headerWithName("X-Test").description("one"))) - .document(this.operationBuilder.response().header("X-test", "test").build()); - assertThat(this.generatedSnippets.responseHeaders()) - .is(tableWithHeader("Name", "Description").row("`X-Test`", "one")); + .document(operationBuilder.response().header("X-test", "test").build()); + assertThat(snippets.responseHeaders()) + .isTable((table) -> table.withHeader("Name", "Description").row("`X-Test`", "one")); } - @Test - public void undocumentedResponseHeader() throws IOException { + @RenderedSnippetTest + void undocumentedResponseHeader(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException { new ResponseHeadersSnippet(Arrays.asList(headerWithName("X-Test").description("one"))) - .document(this.operationBuilder.response().header("X-Test", "test").header("Content-Type", "*/*").build()); - assertThat(this.generatedSnippets.responseHeaders()) - .is(tableWithHeader("Name", "Description").row("`X-Test`", "one")); + .document(operationBuilder.response().header("X-Test", "test").header("Content-Type", "*/*").build()); + assertThat(snippets.responseHeaders()) + .isTable((table) -> table.withHeader("Name", "Description").row("`X-Test`", "one")); } - @Test - public void responseHeadersWithCustomAttributes() throws IOException { - TemplateResourceResolver resolver = mock(TemplateResourceResolver.class); - given(resolver.resolveTemplateResource("response-headers")) - .willReturn(snippetResource("response-headers-with-title")); + @RenderedSnippetTest + @SnippetTemplate(snippet = "response-headers", template = "response-headers-with-title") + void responseHeadersWithCustomAttributes(OperationBuilder operationBuilder, AssertableSnippets snippets) + throws IOException { new ResponseHeadersSnippet(Arrays.asList(headerWithName("X-Test").description("one")), attributes(key("title").value("Custom title"))) - .document(this.operationBuilder - .attribute(TemplateEngine.class.getName(), new MustacheTemplateEngine(resolver)) - .response() - .header("X-Test", "test") - .build()); - assertThat(this.generatedSnippets.responseHeaders()).contains("Custom title"); + .document(operationBuilder.response().header("X-Test", "test").build()); + assertThat(snippets.responseHeaders()).contains("Custom title"); } - @Test - public void responseHeadersWithCustomDescriptorAttributes() throws IOException { - TemplateResourceResolver resolver = mock(TemplateResourceResolver.class); - given(resolver.resolveTemplateResource("response-headers")) - .willReturn(snippetResource("response-headers-with-extra-column")); + @RenderedSnippetTest + @SnippetTemplate(snippet = "response-headers", template = "response-headers-with-extra-column") + void responseHeadersWithCustomDescriptorAttributes(OperationBuilder operationBuilder, AssertableSnippets snippets) + throws IOException { new ResponseHeadersSnippet( Arrays.asList(headerWithName("X-Test").description("one").attributes(key("foo").value("alpha")), headerWithName("Content-Type").description("two").attributes(key("foo").value("bravo")), headerWithName("Etag").description("three").attributes(key("foo").value("charlie")))) - .document(this.operationBuilder - .attribute(TemplateEngine.class.getName(), new MustacheTemplateEngine(resolver)) - .response() + .document(operationBuilder.response() .header("X-Test", "test") .header("Content-Type", "application/json") .header("Etag", "lskjadldj3ii32l2ij23") .build()); - assertThat(this.generatedSnippets.responseHeaders()) - .is(tableWithHeader("Name", "Description", "Foo").row("X-Test", "one", "alpha") - .row("Content-Type", "two", "bravo") - .row("Etag", "three", "charlie")); + assertThat(snippets.responseHeaders()).isTable((table) -> table.withHeader("Name", "Description", "Foo") + .row("X-Test", "one", "alpha") + .row("Content-Type", "two", "bravo") + .row("Etag", "three", "charlie")); } - @Test - public void additionalDescriptors() throws IOException { + @RenderedSnippetTest + void additionalDescriptors(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException { HeaderDocumentation .responseHeaders(headerWithName("X-Test").description("one"), headerWithName("Content-Type").description("two"), headerWithName("Etag").description("three")) .and(headerWithName("Cache-Control").description("five"), headerWithName("Vary").description("six")) - .document(this.operationBuilder.response() + .document(operationBuilder.response() .header("X-Test", "test") .header("Content-Type", "application/json") .header("Etag", "lskjadldj3ii32l2ij23") .header("Cache-Control", "max-age=0") .header("Vary", "User-Agent") .build()); - assertThat(this.generatedSnippets.responseHeaders()) - .is(tableWithHeader("Name", "Description").row("`X-Test`", "one") - .row("`Content-Type`", "two") - .row("`Etag`", "three") - .row("`Cache-Control`", "five") - .row("`Vary`", "six")); + assertThat(snippets.responseHeaders()).isTable((table) -> table.withHeader("Name", "Description") + .row("`X-Test`", "one") + .row("`Content-Type`", "two") + .row("`Etag`", "three") + .row("`Cache-Control`", "five") + .row("`Vary`", "six")); } - @Test - public void tableCellContentIsEscapedWhenNecessary() throws IOException { + @RenderedSnippetTest + void tableCellContentIsEscapedWhenNecessary(OperationBuilder operationBuilder, AssertableSnippets snippets) + throws IOException { new ResponseHeadersSnippet(Arrays.asList(headerWithName("Foo|Bar").description("one|two"))) - .document(this.operationBuilder.response().header("Foo|Bar", "baz").build()); - assertThat(this.generatedSnippets.responseHeaders()).is(tableWithHeader("Name", "Description") - .row(escapeIfNecessary("`Foo|Bar`"), escapeIfNecessary("one|two"))); + .document(operationBuilder.response().header("Foo|Bar", "baz").build()); + assertThat(snippets.responseHeaders()) + .isTable((table) -> table.withHeader("Name", "Description").row("`Foo|Bar`", "one|two")); + } + + @SnippetTest + void missingResponseHeader(OperationBuilder operationBuilder) { + assertThatExceptionOfType(SnippetException.class) + .isThrownBy( + () -> new ResponseHeadersSnippet(Arrays.asList(headerWithName("Content-Type").description("one"))) + .document(operationBuilder.response().build())) + .withMessage("Headers with the following names were not found" + " in the response: [Content-Type]"); } - private String escapeIfNecessary(String input) { - if (this.templateFormat.getId().equals(TemplateFormats.markdown().getId())) { - return input; - } - return input.replace("|", "\\|"); + @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]"); } } diff --git a/spring-restdocs-core/src/test/java/org/springframework/restdocs/http/HttpRequestSnippetTests.java b/spring-restdocs-core/src/test/java/org/springframework/restdocs/http/HttpRequestSnippetTests.java index a3ff9386..203084d3 100644 --- a/spring-restdocs-core/src/test/java/org/springframework/restdocs/http/HttpRequestSnippetTests.java +++ b/spring-restdocs-core/src/test/java/org/springframework/restdocs/http/HttpRequestSnippetTests.java @@ -18,20 +18,14 @@ import java.io.IOException; -import org.junit.Test; - import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; -import org.springframework.restdocs.AbstractSnippetTests; -import org.springframework.restdocs.templates.TemplateEngine; -import org.springframework.restdocs.templates.TemplateFormat; -import org.springframework.restdocs.templates.TemplateResourceResolver; -import org.springframework.restdocs.templates.mustache.MustacheTemplateEngine; -import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.restdocs.testfixtures.jupiter.AssertableSnippets; +import org.springframework.restdocs.testfixtures.jupiter.OperationBuilder; +import org.springframework.restdocs.testfixtures.jupiter.RenderedSnippetTest; +import org.springframework.restdocs.testfixtures.jupiter.SnippetTemplate; import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.BDDMockito.given; -import static org.mockito.Mockito.mock; import static org.springframework.restdocs.snippet.Attributes.attributes; import static org.springframework.restdocs.snippet.Attributes.key; @@ -41,162 +35,160 @@ * @author Andy Wilkinson * @author Jonathan Pearlin */ -public class HttpRequestSnippetTests extends AbstractSnippetTests { +class HttpRequestSnippetTests { private static final String BOUNDARY = "6o2knFse3p53ty9dmcQvWAIx1zInP11uCfbm"; - public HttpRequestSnippetTests(String name, TemplateFormat templateFormat) { - super(name, templateFormat); - } - - @Test - public void getRequest() throws IOException { + @RenderedSnippetTest + void getRequest(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException { new HttpRequestSnippet() - .document(this.operationBuilder.request("http://localhost/foo").header("Alpha", "a").build()); - assertThat(this.generatedSnippets.httpRequest()) - .is(httpRequest(RequestMethod.GET, "/foo").header("Alpha", "a").header(HttpHeaders.HOST, "localhost")); + .document(operationBuilder.request("http://localhost/foo").header("Alpha", "a").build()); + assertThat(snippets.httpRequest()) + .isHttpRequest((request) -> request.get("/foo").header("Alpha", "a").header(HttpHeaders.HOST, "localhost")); } - @Test - public void getRequestWithQueryParameters() throws IOException { + @RenderedSnippetTest + void getRequestWithQueryParameters(OperationBuilder operationBuilder, AssertableSnippets snippets) + throws IOException { new HttpRequestSnippet() - .document(this.operationBuilder.request("http://localhost/foo?b=bravo").header("Alpha", "a").build()); - assertThat(this.generatedSnippets.httpRequest()) - .is(httpRequest(RequestMethod.GET, "/foo?b=bravo").header("Alpha", "a") - .header(HttpHeaders.HOST, "localhost")); + .document(operationBuilder.request("http://localhost/foo?b=bravo").header("Alpha", "a").build()); + assertThat(snippets.httpRequest()).isHttpRequest( + (request) -> request.get("/foo?b=bravo").header("Alpha", "a").header(HttpHeaders.HOST, "localhost")); } - @Test - public void getRequestWithPort() throws IOException { + @RenderedSnippetTest + void getRequestWithPort(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException { new HttpRequestSnippet() - .document(this.operationBuilder.request("http://localhost:8080/foo").header("Alpha", "a").build()); - assertThat(this.generatedSnippets.httpRequest()) - .is(httpRequest(RequestMethod.GET, "/foo").header("Alpha", "a").header(HttpHeaders.HOST, "localhost:8080")); + .document(operationBuilder.request("http://localhost:8080/foo").header("Alpha", "a").build()); + assertThat(snippets.httpRequest()).isHttpRequest( + (request) -> request.get("/foo").header("Alpha", "a").header(HttpHeaders.HOST, "localhost:8080")); } - @Test - public void getRequestWithCookies() throws IOException { - new HttpRequestSnippet().document(this.operationBuilder.request("http://localhost/foo") + @RenderedSnippetTest + void getRequestWithCookies(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException { + new HttpRequestSnippet().document(operationBuilder.request("http://localhost/foo") .cookie("name1", "value1") .cookie("name2", "value2") .build()); - assertThat(this.generatedSnippets.httpRequest()) - .is(httpRequest(RequestMethod.GET, "/foo").header(HttpHeaders.HOST, "localhost") - .header(HttpHeaders.COOKIE, "name1=value1; name2=value2")); + assertThat(snippets.httpRequest()).isHttpRequest((request) -> request.get("/foo") + .header(HttpHeaders.HOST, "localhost") + .header(HttpHeaders.COOKIE, "name1=value1; name2=value2")); } - @Test - public void getRequestWithQueryString() throws IOException { - new HttpRequestSnippet().document(this.operationBuilder.request("http://localhost/foo?bar=baz").build()); - assertThat(this.generatedSnippets.httpRequest()) - .is(httpRequest(RequestMethod.GET, "/foo?bar=baz").header(HttpHeaders.HOST, "localhost")); + @RenderedSnippetTest + void getRequestWithQueryString(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException { + new HttpRequestSnippet().document(operationBuilder.request("http://localhost/foo?bar=baz").build()); + assertThat(snippets.httpRequest()) + .isHttpRequest((request) -> request.get("/foo?bar=baz").header(HttpHeaders.HOST, "localhost")); } - @Test - public void getRequestWithQueryStringWithNoValue() throws IOException { - new HttpRequestSnippet().document(this.operationBuilder.request("http://localhost/foo?bar").build()); - assertThat(this.generatedSnippets.httpRequest()) - .is(httpRequest(RequestMethod.GET, "/foo?bar").header(HttpHeaders.HOST, "localhost")); + @RenderedSnippetTest + void getRequestWithQueryStringWithNoValue(OperationBuilder operationBuilder, AssertableSnippets snippets) + throws IOException { + new HttpRequestSnippet().document(operationBuilder.request("http://localhost/foo?bar").build()); + assertThat(snippets.httpRequest()) + .isHttpRequest((request) -> request.get("/foo?bar").header(HttpHeaders.HOST, "localhost")); } - @Test - public void postRequestWithContent() throws IOException { + @RenderedSnippetTest + void postRequestWithContent(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException { String content = "Hello, world"; new HttpRequestSnippet() - .document(this.operationBuilder.request("http://localhost/foo").method("POST").content(content).build()); - assertThat(this.generatedSnippets.httpRequest()) - .is(httpRequest(RequestMethod.POST, "/foo").header(HttpHeaders.HOST, "localhost") - .content(content) - .header(HttpHeaders.CONTENT_LENGTH, content.getBytes().length)); + .document(operationBuilder.request("http://localhost/foo").method("POST").content(content).build()); + assertThat(snippets.httpRequest()).isHttpRequest((request) -> request.post("/foo") + .header(HttpHeaders.HOST, "localhost") + .content(content) + .header(HttpHeaders.CONTENT_LENGTH, content.getBytes().length)); } - @Test - public void postRequestWithContentAndQueryParameters() throws IOException { + @RenderedSnippetTest + void postRequestWithContentAndQueryParameters(OperationBuilder operationBuilder, AssertableSnippets snippets) + throws IOException { String content = "Hello, world"; - new HttpRequestSnippet().document( - this.operationBuilder.request("http://localhost/foo?a=alpha").method("POST").content(content).build()); - assertThat(this.generatedSnippets.httpRequest()) - .is(httpRequest(RequestMethod.POST, "/foo?a=alpha").header(HttpHeaders.HOST, "localhost") - .content(content) - .header(HttpHeaders.CONTENT_LENGTH, content.getBytes().length)); + new HttpRequestSnippet() + .document(operationBuilder.request("http://localhost/foo?a=alpha").method("POST").content(content).build()); + assertThat(snippets.httpRequest()).isHttpRequest((request) -> request.post("/foo?a=alpha") + .header(HttpHeaders.HOST, "localhost") + .content(content) + .header(HttpHeaders.CONTENT_LENGTH, content.getBytes().length)); } - @Test - public void postRequestWithCharset() throws IOException { + @RenderedSnippetTest + void postRequestWithCharset(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException { String japaneseContent = "\u30b3\u30f3\u30c6\u30f3\u30c4"; byte[] contentBytes = japaneseContent.getBytes("UTF-8"); - new HttpRequestSnippet().document(this.operationBuilder.request("http://localhost/foo") + new HttpRequestSnippet().document(operationBuilder.request("http://localhost/foo") .method("POST") .header("Content-Type", "text/plain;charset=UTF-8") .content(contentBytes) .build()); - assertThat(this.generatedSnippets.httpRequest()) - .is(httpRequest(RequestMethod.POST, "/foo").header("Content-Type", "text/plain;charset=UTF-8") - .header(HttpHeaders.HOST, "localhost") - .header(HttpHeaders.CONTENT_LENGTH, contentBytes.length) - .content(japaneseContent)); + assertThat(snippets.httpRequest()).isHttpRequest((request) -> request.post("/foo") + .header("Content-Type", "text/plain;charset=UTF-8") + .header(HttpHeaders.HOST, "localhost") + .header(HttpHeaders.CONTENT_LENGTH, contentBytes.length) + .content(japaneseContent)); } - @Test - public void putRequestWithContent() throws IOException { + @RenderedSnippetTest + void putRequestWithContent(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException { String content = "Hello, world"; new HttpRequestSnippet() - .document(this.operationBuilder.request("http://localhost/foo").method("PUT").content(content).build()); - assertThat(this.generatedSnippets.httpRequest()) - .is(httpRequest(RequestMethod.PUT, "/foo").header(HttpHeaders.HOST, "localhost") - .content(content) - .header(HttpHeaders.CONTENT_LENGTH, content.getBytes().length)); + .document(operationBuilder.request("http://localhost/foo").method("PUT").content(content).build()); + assertThat(snippets.httpRequest()).isHttpRequest((request) -> request.put("/foo") + .header(HttpHeaders.HOST, "localhost") + .content(content) + .header(HttpHeaders.CONTENT_LENGTH, content.getBytes().length)); } - @Test - public void multipartPost() throws IOException { - new HttpRequestSnippet().document(this.operationBuilder.request("http://localhost/upload") + @RenderedSnippetTest + void multipartPost(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException { + new HttpRequestSnippet().document(operationBuilder.request("http://localhost/upload") .method("POST") .header(HttpHeaders.CONTENT_TYPE, MediaType.MULTIPART_FORM_DATA_VALUE) .part("image", "<< data >>".getBytes()) .build()); String expectedContent = createPart( String.format("Content-Disposition: " + "form-data; " + "name=image%n%n<< data >>")); - assertThat(this.generatedSnippets.httpRequest()).is(httpRequest(RequestMethod.POST, "/upload") + assertThat(snippets.httpRequest()).isHttpRequest((request) -> request.post("/upload") .header("Content-Type", "multipart/form-data; boundary=" + BOUNDARY) .header(HttpHeaders.HOST, "localhost") .content(expectedContent)); } - @Test - public void multipartPut() throws IOException { - new HttpRequestSnippet().document(this.operationBuilder.request("http://localhost/upload") + @RenderedSnippetTest + void multipartPut(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException { + new HttpRequestSnippet().document(operationBuilder.request("http://localhost/upload") .method("PUT") .header(HttpHeaders.CONTENT_TYPE, MediaType.MULTIPART_FORM_DATA_VALUE) .part("image", "<< data >>".getBytes()) .build()); String expectedContent = createPart( String.format("Content-Disposition: " + "form-data; " + "name=image%n%n<< data >>")); - assertThat(this.generatedSnippets.httpRequest()).is(httpRequest(RequestMethod.PUT, "/upload") + assertThat(snippets.httpRequest()).isHttpRequest((request) -> request.put("/upload") .header("Content-Type", "multipart/form-data; boundary=" + BOUNDARY) .header(HttpHeaders.HOST, "localhost") .content(expectedContent)); } - @Test - public void multipartPatch() throws IOException { - new HttpRequestSnippet().document(this.operationBuilder.request("http://localhost/upload") + @RenderedSnippetTest + void multipartPatch(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException { + new HttpRequestSnippet().document(operationBuilder.request("http://localhost/upload") .method("PATCH") .header(HttpHeaders.CONTENT_TYPE, MediaType.MULTIPART_FORM_DATA_VALUE) .part("image", "<< data >>".getBytes()) .build()); String expectedContent = createPart( String.format("Content-Disposition: " + "form-data; " + "name=image%n%n<< data >>")); - assertThat(this.generatedSnippets.httpRequest()).is(httpRequest(RequestMethod.PATCH, "/upload") + assertThat(snippets.httpRequest()).isHttpRequest((request) -> request.patch("/upload") .header("Content-Type", "multipart/form-data; boundary=" + BOUNDARY) .header(HttpHeaders.HOST, "localhost") .content(expectedContent)); } - @Test - public void multipartPostWithFilename() throws IOException { - new HttpRequestSnippet().document(this.operationBuilder.request("http://localhost/upload") + @RenderedSnippetTest + void multipartPostWithFilename(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException { + new HttpRequestSnippet().document(operationBuilder.request("http://localhost/upload") .method("POST") .header(HttpHeaders.CONTENT_TYPE, MediaType.MULTIPART_FORM_DATA_VALUE) .part("image", "<< data >>".getBytes()) @@ -204,15 +196,16 @@ public void multipartPostWithFilename() throws IOException { .build()); String expectedContent = createPart(String .format("Content-Disposition: " + "form-data; " + "name=image; filename=image.png%n%n<< data >>")); - assertThat(this.generatedSnippets.httpRequest()).is(httpRequest(RequestMethod.POST, "/upload") + assertThat(snippets.httpRequest()).isHttpRequest((request) -> request.post("/upload") .header("Content-Type", "multipart/form-data; boundary=" + BOUNDARY) .header(HttpHeaders.HOST, "localhost") .content(expectedContent)); } - @Test - public void multipartPostWithContentType() throws IOException { - new HttpRequestSnippet().document(this.operationBuilder.request("http://localhost/upload") + @RenderedSnippetTest + void multipartPostWithContentType(OperationBuilder operationBuilder, AssertableSnippets snippets) + throws IOException { + new HttpRequestSnippet().document(operationBuilder.request("http://localhost/upload") .method("POST") .header(HttpHeaders.CONTENT_TYPE, MediaType.MULTIPART_FORM_DATA_VALUE) .part("image", "<< data >>".getBytes()) @@ -220,38 +213,35 @@ public void multipartPostWithContentType() throws IOException { .build()); String expectedContent = createPart(String .format("Content-Disposition: form-data; name=image%nContent-Type: " + "image/png%n%n<< data >>")); - assertThat(this.generatedSnippets.httpRequest()).is(httpRequest(RequestMethod.POST, "/upload") + assertThat(snippets.httpRequest()).isHttpRequest((request) -> request.post("/upload") .header("Content-Type", "multipart/form-data; boundary=" + BOUNDARY) .header(HttpHeaders.HOST, "localhost") .content(expectedContent)); } - @Test - public void getRequestWithCustomHost() throws IOException { - new HttpRequestSnippet().document(this.operationBuilder.request("http://localhost/foo") - .header(HttpHeaders.HOST, "api.example.com") - .build()); - assertThat(this.generatedSnippets.httpRequest()) - .is(httpRequest(RequestMethod.GET, "/foo").header(HttpHeaders.HOST, "api.example.com")); + @RenderedSnippetTest + void getRequestWithCustomHost(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException { + new HttpRequestSnippet().document( + operationBuilder.request("http://localhost/foo").header(HttpHeaders.HOST, "api.example.com").build()); + assertThat(snippets.httpRequest()) + .isHttpRequest((request) -> request.get("/foo").header(HttpHeaders.HOST, "api.example.com")); } - @Test - public void requestWithCustomSnippetAttributes() throws IOException { - TemplateResourceResolver resolver = mock(TemplateResourceResolver.class); - given(resolver.resolveTemplateResource("http-request")).willReturn(snippetResource("http-request-with-title")); - new HttpRequestSnippet(attributes(key("title").value("Title for the request"))).document( - this.operationBuilder.attribute(TemplateEngine.class.getName(), new MustacheTemplateEngine(resolver)) - .request("http://localhost/foo") - .build()); - assertThat(this.generatedSnippets.httpRequest()).contains("Title for the request"); + @RenderedSnippetTest + @SnippetTemplate(snippet = "http-request", template = "http-request-with-title") + void requestWithCustomSnippetAttributes(OperationBuilder operationBuilder, AssertableSnippets snippets) + throws IOException { + new HttpRequestSnippet(attributes(key("title").value("Title for the request"))) + .document(operationBuilder.request("http://localhost/foo").build()); + assertThat(snippets.httpRequest()).contains("Title for the request"); } - @Test - public void deleteWithQueryString() throws IOException { + @RenderedSnippetTest + void deleteWithQueryString(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException { new HttpRequestSnippet() - .document(this.operationBuilder.request("http://localhost/foo?a=alpha&b=bravo").method("DELETE").build()); - assertThat(this.generatedSnippets.httpRequest()) - .is(httpRequest(RequestMethod.DELETE, "/foo?a=alpha&b=bravo").header("Host", "localhost")); + .document(operationBuilder.request("http://localhost/foo?a=alpha&b=bravo").method("DELETE").build()); + assertThat(snippets.httpRequest()) + .isHttpRequest((request) -> request.delete("/foo?a=alpha&b=bravo").header("Host", "localhost")); } private String createPart(String content) { diff --git a/spring-restdocs-core/src/test/java/org/springframework/restdocs/http/HttpResponseSnippetTests.java b/spring-restdocs-core/src/test/java/org/springframework/restdocs/http/HttpResponseSnippetTests.java index beef98ae..7ffcf69c 100644 --- a/spring-restdocs-core/src/test/java/org/springframework/restdocs/http/HttpResponseSnippetTests.java +++ b/spring-restdocs-core/src/test/java/org/springframework/restdocs/http/HttpResponseSnippetTests.java @@ -18,21 +18,16 @@ import java.io.IOException; -import org.junit.Test; - import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatusCode; import org.springframework.http.MediaType; -import org.springframework.restdocs.AbstractSnippetTests; -import org.springframework.restdocs.templates.TemplateEngine; -import org.springframework.restdocs.templates.TemplateFormat; -import org.springframework.restdocs.templates.TemplateResourceResolver; -import org.springframework.restdocs.templates.mustache.MustacheTemplateEngine; +import org.springframework.restdocs.testfixtures.jupiter.AssertableSnippets; +import org.springframework.restdocs.testfixtures.jupiter.OperationBuilder; +import org.springframework.restdocs.testfixtures.jupiter.RenderedSnippetTest; +import org.springframework.restdocs.testfixtures.jupiter.SnippetTemplate; import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.BDDMockito.given; -import static org.mockito.Mockito.mock; import static org.springframework.restdocs.snippet.Attributes.attributes; import static org.springframework.restdocs.snippet.Attributes.key; @@ -42,72 +37,66 @@ * @author Andy Wilkinson * @author Jonathan Pearlin */ -public class HttpResponseSnippetTests extends AbstractSnippetTests { - - public HttpResponseSnippetTests(String name, TemplateFormat templateFormat) { - super(name, templateFormat); - } +class HttpResponseSnippetTests { - @Test - public void basicResponse() throws IOException { - new HttpResponseSnippet().document(this.operationBuilder.build()); - assertThat(this.generatedSnippets.httpResponse()).is(httpResponse(HttpStatus.OK)); + @RenderedSnippetTest + void basicResponse(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException { + new HttpResponseSnippet().document(operationBuilder.build()); + assertThat(snippets.httpResponse()).isHttpResponse((response) -> response.ok()); } - @Test - public void nonOkResponse() throws IOException { - new HttpResponseSnippet().document(this.operationBuilder.response().status(HttpStatus.BAD_REQUEST).build()); - assertThat(this.generatedSnippets.httpResponse()).is(httpResponse(HttpStatus.BAD_REQUEST)); + @RenderedSnippetTest + void nonOkResponse(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException { + new HttpResponseSnippet().document(operationBuilder.response().status(HttpStatus.BAD_REQUEST).build()); + assertThat(snippets.httpResponse()).isHttpResponse((response) -> response.badRequest()); } - @Test - public void responseWithHeaders() throws IOException { - new HttpResponseSnippet().document(this.operationBuilder.response() + @RenderedSnippetTest + void responseWithHeaders(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException { + new HttpResponseSnippet().document(operationBuilder.response() .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) .header("a", "alpha") .build()); - assertThat(this.generatedSnippets.httpResponse()) - .is(httpResponse(HttpStatus.OK).header("Content-Type", "application/json").header("a", "alpha")); + assertThat(snippets.httpResponse()).isHttpResponse( + (response) -> response.ok().header("Content-Type", "application/json").header("a", "alpha")); } - @Test - public void responseWithContent() throws IOException { + @RenderedSnippetTest + void responseWithContent(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException { String content = "content"; - new HttpResponseSnippet().document(this.operationBuilder.response().content(content).build()); - assertThat(this.generatedSnippets.httpResponse()).is(httpResponse(HttpStatus.OK).content(content) + new HttpResponseSnippet().document(operationBuilder.response().content(content).build()); + assertThat(snippets.httpResponse()).isHttpResponse((response) -> response.ok() + .content(content) .header(HttpHeaders.CONTENT_LENGTH, content.getBytes().length)); } - @Test - public void responseWithCharset() throws IOException { + @RenderedSnippetTest + void responseWithCharset(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException { String japaneseContent = "\u30b3\u30f3\u30c6\u30f3\u30c4"; byte[] contentBytes = japaneseContent.getBytes("UTF-8"); - new HttpResponseSnippet().document(this.operationBuilder.response() + new HttpResponseSnippet().document(operationBuilder.response() .header("Content-Type", "text/plain;charset=UTF-8") .content(contentBytes) .build()); - assertThat(this.generatedSnippets.httpResponse()) - .is(httpResponse(HttpStatus.OK).header("Content-Type", "text/plain;charset=UTF-8") - .content(japaneseContent) - .header(HttpHeaders.CONTENT_LENGTH, contentBytes.length)); + assertThat(snippets.httpResponse()).isHttpResponse((response) -> response.ok() + .header("Content-Type", "text/plain;charset=UTF-8") + .content(japaneseContent) + .header(HttpHeaders.CONTENT_LENGTH, contentBytes.length)); } - @Test - public void responseWithCustomSnippetAttributes() throws IOException { - TemplateResourceResolver resolver = mock(TemplateResourceResolver.class); - given(resolver.resolveTemplateResource("http-response")) - .willReturn(snippetResource("http-response-with-title")); - new HttpResponseSnippet(attributes(key("title").value("Title for the response"))).document( - this.operationBuilder.attribute(TemplateEngine.class.getName(), new MustacheTemplateEngine(resolver)) - .build()); - assertThat(this.generatedSnippets.httpResponse()).contains("Title for the response"); + @RenderedSnippetTest + @SnippetTemplate(snippet = "http-response", template = "http-response-with-title") + void responseWithCustomSnippetAttributes(OperationBuilder operationBuilder, AssertableSnippets snippets) + throws IOException { + new HttpResponseSnippet(attributes(key("title").value("Title for the response"))) + .document(operationBuilder.build()); + assertThat(snippets.httpResponse()).contains("Title for the response"); } - @Test - public void responseWithCustomStatus() throws IOException { - new HttpResponseSnippet() - .document(this.operationBuilder.response().status(HttpStatusCode.valueOf(215)).build()); - assertThat(this.generatedSnippets.httpResponse()).is(httpResponse(215)); + @RenderedSnippetTest + void responseWithCustomStatus(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException { + new HttpResponseSnippet().document(operationBuilder.response().status(HttpStatusCode.valueOf(215)).build()); + assertThat(snippets.httpResponse()).isHttpResponse(((response) -> response.status(215))); } } diff --git a/spring-restdocs-core/src/test/java/org/springframework/restdocs/hypermedia/ContentTypeLinkExtractorTests.java b/spring-restdocs-core/src/test/java/org/springframework/restdocs/hypermedia/ContentTypeLinkExtractorTests.java index 69899c3d..2d156c9e 100644 --- a/spring-restdocs-core/src/test/java/org/springframework/restdocs/hypermedia/ContentTypeLinkExtractorTests.java +++ b/spring-restdocs-core/src/test/java/org/springframework/restdocs/hypermedia/ContentTypeLinkExtractorTests.java @@ -18,9 +18,10 @@ import java.io.IOException; import java.util.HashMap; +import java.util.List; import java.util.Map; -import org.junit.Test; +import org.junit.jupiter.api.Test; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; @@ -28,6 +29,7 @@ import org.springframework.restdocs.operation.OperationResponse; import org.springframework.restdocs.operation.OperationResponseFactory; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatIllegalStateException; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; @@ -37,18 +39,20 @@ * * @author Andy Wilkinson */ -public class ContentTypeLinkExtractorTests { +class ContentTypeLinkExtractorTests { private final OperationResponseFactory responseFactory = new OperationResponseFactory(); + private final String halBody = "{ \"_links\" : { \"someRel\" : { \"href\" : \"someHref\" }} }"; + @Test - public void extractionFailsWithNullContentType() { + void extractionFailsWithNullContentType() { assertThatIllegalStateException().isThrownBy(() -> new ContentTypeLinkExtractor() .extractLinks(this.responseFactory.create(HttpStatus.OK, new HttpHeaders(), null))); } @Test - public void extractorCalledWithMatchingContextType() throws IOException { + void extractorCalledWithMatchingContextType() throws IOException { Map extractors = new HashMap<>(); LinkExtractor extractor = mock(LinkExtractor.class); extractors.put(MediaType.APPLICATION_JSON, extractor); @@ -60,7 +64,7 @@ public void extractorCalledWithMatchingContextType() throws IOException { } @Test - public void extractorCalledWithCompatibleContextType() throws IOException { + void extractorCalledWithCompatibleContextType() throws IOException { Map extractors = new HashMap<>(); LinkExtractor extractor = mock(LinkExtractor.class); extractors.put(MediaType.APPLICATION_JSON, extractor); @@ -71,4 +75,22 @@ public void extractorCalledWithCompatibleContextType() throws IOException { verify(extractor).extractLinks(response); } + @Test + void extractsLinksFromVndHalMediaType() throws IOException { + HttpHeaders httpHeaders = new HttpHeaders(); + httpHeaders.setContentType(MediaType.parseMediaType("application/vnd.hal+json")); + OperationResponse response = this.responseFactory.create(HttpStatus.OK, httpHeaders, this.halBody.getBytes()); + Map> links = new ContentTypeLinkExtractor().extractLinks(response); + assertThat(links).containsKey("someRel"); + } + + @Test + void extractsLinksFromHalFormsMediaType() throws IOException { + HttpHeaders httpHeaders = new HttpHeaders(); + httpHeaders.setContentType(MediaType.parseMediaType("application/prs.hal-forms+json")); + OperationResponse response = this.responseFactory.create(HttpStatus.OK, httpHeaders, this.halBody.getBytes()); + Map> links = new ContentTypeLinkExtractor().extractLinks(response); + assertThat(links).containsKey("someRel"); + } + } diff --git a/spring-restdocs-core/src/test/java/org/springframework/restdocs/hypermedia/LinkExtractorsPayloadTests.java b/spring-restdocs-core/src/test/java/org/springframework/restdocs/hypermedia/LinkExtractorsPayloadTests.java index 75c64154..33416ea5 100644 --- a/spring-restdocs-core/src/test/java/org/springframework/restdocs/hypermedia/LinkExtractorsPayloadTests.java +++ b/spring-restdocs-core/src/test/java/org/springframework/restdocs/hypermedia/LinkExtractorsPayloadTests.java @@ -24,10 +24,9 @@ import java.util.List; import java.util.Map; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; -import org.junit.runners.Parameterized.Parameters; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedClass; +import org.junit.jupiter.params.provider.MethodSource; import org.springframework.http.HttpStatus; import org.springframework.restdocs.operation.OperationResponse; @@ -39,13 +38,13 @@ import static org.assertj.core.api.Assertions.assertThat; /** - * Parameterized tests for {@link HalLinkExtractor} and {@link AtomLinkExtractor} with - * various payloads. + * Tests for {@link HalLinkExtractor} and {@link AtomLinkExtractor} with various payloads. * * @author Andy Wilkinson */ -@RunWith(Parameterized.class) -public class LinkExtractorsPayloadTests { +@ParameterizedClass(name = "{1}") +@MethodSource("parameters") +class LinkExtractorsPayloadTests { private final OperationResponseFactory responseFactory = new OperationResponseFactory(); @@ -53,25 +52,24 @@ public class LinkExtractorsPayloadTests { private final String linkType; - @Parameters(name = "{1}") - public static Collection data() { + static Collection parameters() { return Arrays.asList(new Object[] { new HalLinkExtractor(), "hal" }, new Object[] { new AtomLinkExtractor(), "atom" }); } - public LinkExtractorsPayloadTests(LinkExtractor linkExtractor, String linkType) { + LinkExtractorsPayloadTests(LinkExtractor linkExtractor, String linkType) { this.linkExtractor = linkExtractor; this.linkType = linkType; } @Test - public void singleLink() throws IOException { + void singleLink() throws IOException { Map> links = this.linkExtractor.extractLinks(createResponse("single-link")); assertLinks(Arrays.asList(new Link("alpha", "https://alpha.example.com", "Alpha")), links); } @Test - public void multipleLinksWithDifferentRels() throws IOException { + void multipleLinksWithDifferentRels() throws IOException { Map> links = this.linkExtractor .extractLinks(createResponse("multiple-links-different-rels")); assertLinks(Arrays.asList(new Link("alpha", "https://alpha.example.com", "Alpha"), @@ -79,20 +77,20 @@ public void multipleLinksWithDifferentRels() throws IOException { } @Test - public void multipleLinksWithSameRels() throws IOException { + void multipleLinksWithSameRels() throws IOException { Map> links = this.linkExtractor.extractLinks(createResponse("multiple-links-same-rels")); assertLinks(Arrays.asList(new Link("alpha", "https://alpha.example.com/one", "Alpha one"), new Link("alpha", "https://alpha.example.com/two")), links); } @Test - public void noLinks() throws IOException { + void noLinks() throws IOException { Map> links = this.linkExtractor.extractLinks(createResponse("no-links")); assertLinks(Collections.emptyList(), links); } @Test - public void linksInTheWrongFormat() throws IOException { + void linksInTheWrongFormat() throws IOException { Map> links = this.linkExtractor.extractLinks(createResponse("wrong-format")); assertLinks(Collections.emptyList(), links); } diff --git a/spring-restdocs-core/src/test/java/org/springframework/restdocs/hypermedia/LinksSnippetFailureTests.java b/spring-restdocs-core/src/test/java/org/springframework/restdocs/hypermedia/LinksSnippetFailureTests.java deleted file mode 100644 index b37df8a2..00000000 --- a/spring-restdocs-core/src/test/java/org/springframework/restdocs/hypermedia/LinksSnippetFailureTests.java +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright 2014-present 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.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"); - } - -} diff --git a/spring-restdocs-core/src/test/java/org/springframework/restdocs/hypermedia/LinksSnippetTests.java b/spring-restdocs-core/src/test/java/org/springframework/restdocs/hypermedia/LinksSnippetTests.java index 036ac459..fac1a687 100644 --- a/spring-restdocs-core/src/test/java/org/springframework/restdocs/hypermedia/LinksSnippetTests.java +++ b/spring-restdocs-core/src/test/java/org/springframework/restdocs/hypermedia/LinksSnippetTests.java @@ -18,19 +18,17 @@ import java.io.IOException; import java.util.Arrays; +import java.util.Collections; -import org.junit.Test; - -import org.springframework.restdocs.AbstractSnippetTests; -import org.springframework.restdocs.templates.TemplateEngine; -import org.springframework.restdocs.templates.TemplateFormat; -import org.springframework.restdocs.templates.TemplateFormats; -import org.springframework.restdocs.templates.TemplateResourceResolver; -import org.springframework.restdocs.templates.mustache.MustacheTemplateEngine; +import org.springframework.restdocs.snippet.SnippetException; +import org.springframework.restdocs.testfixtures.jupiter.AssertableSnippets; +import org.springframework.restdocs.testfixtures.jupiter.OperationBuilder; +import org.springframework.restdocs.testfixtures.jupiter.RenderedSnippetTest; +import org.springframework.restdocs.testfixtures.jupiter.SnippetTemplate; +import org.springframework.restdocs.testfixtures.jupiter.SnippetTest; import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.BDDMockito.given; -import static org.mockito.Mockito.mock; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.springframework.restdocs.snippet.Attributes.attributes; import static org.springframework.restdocs.snippet.Attributes.key; @@ -39,115 +37,145 @@ * * @author Andy Wilkinson */ -public class LinksSnippetTests extends AbstractSnippetTests { - - public LinksSnippetTests(String name, TemplateFormat templateFormat) { - super(name, templateFormat); - } +class LinksSnippetTests { - @Test - public void ignoredLink() throws IOException { + @RenderedSnippetTest + void ignoredLink(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException { new LinksSnippet(new StubLinkExtractor().withLinks(new Link("a", "alpha"), new Link("b", "bravo")), Arrays.asList(new LinkDescriptor("a").ignored(), new LinkDescriptor("b").description("Link b"))) - .document(this.operationBuilder.build()); - assertThat(this.generatedSnippets.links()).is(tableWithHeader("Relation", "Description").row("`b`", "Link b")); + .document(operationBuilder.build()); + assertThat(snippets.links()) + .isTable((table) -> table.withHeader("Relation", "Description").row("`b`", "Link b")); } - @Test - public void allUndocumentedLinksCanBeIgnored() throws IOException { + @RenderedSnippetTest + void allUndocumentedLinksCanBeIgnored(OperationBuilder operationBuilder, AssertableSnippets snippets) + throws IOException { new LinksSnippet(new StubLinkExtractor().withLinks(new Link("a", "alpha"), new Link("b", "bravo")), Arrays.asList(new LinkDescriptor("b").description("Link b")), true) - .document(this.operationBuilder.build()); - assertThat(this.generatedSnippets.links()).is(tableWithHeader("Relation", "Description").row("`b`", "Link b")); + .document(operationBuilder.build()); + assertThat(snippets.links()) + .isTable((table) -> table.withHeader("Relation", "Description").row("`b`", "Link b")); } - @Test - public void presentOptionalLink() throws IOException { + @RenderedSnippetTest + void presentOptionalLink(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException { new LinksSnippet(new StubLinkExtractor().withLinks(new Link("foo", "blah")), Arrays.asList(new LinkDescriptor("foo").description("bar").optional())) - .document(this.operationBuilder.build()); - assertThat(this.generatedSnippets.links()).is(tableWithHeader("Relation", "Description").row("`foo`", "bar")); + .document(operationBuilder.build()); + assertThat(snippets.links()) + .isTable((table) -> table.withHeader("Relation", "Description").row("`foo`", "bar")); } - @Test - public void missingOptionalLink() throws IOException { + @RenderedSnippetTest + void missingOptionalLink(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException { new LinksSnippet(new StubLinkExtractor(), Arrays.asList(new LinkDescriptor("foo").description("bar").optional())) - .document(this.operationBuilder.build()); - assertThat(this.generatedSnippets.links()).is(tableWithHeader("Relation", "Description").row("`foo`", "bar")); + .document(operationBuilder.build()); + assertThat(snippets.links()) + .isTable((table) -> table.withHeader("Relation", "Description").row("`foo`", "bar")); } - @Test - public void documentedLinks() throws IOException { + @RenderedSnippetTest + void documentedLinks(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException { new LinksSnippet(new StubLinkExtractor().withLinks(new Link("a", "alpha"), new Link("b", "bravo")), Arrays.asList(new LinkDescriptor("a").description("one"), new LinkDescriptor("b").description("two"))) - .document(this.operationBuilder.build()); - assertThat(this.generatedSnippets.links()) - .is(tableWithHeader("Relation", "Description").row("`a`", "one").row("`b`", "two")); + .document(operationBuilder.build()); + assertThat(snippets.links()) + .isTable((table) -> table.withHeader("Relation", "Description").row("`a`", "one").row("`b`", "two")); } - @Test - public void linkDescriptionFromTitleInPayload() throws IOException { + @RenderedSnippetTest + void linkDescriptionFromTitleInPayload(OperationBuilder operationBuilder, AssertableSnippets snippets) + throws IOException { new LinksSnippet( new StubLinkExtractor().withLinks(new Link("a", "alpha", "Link a"), new Link("b", "bravo", "Link b")), Arrays.asList(new LinkDescriptor("a").description("one"), new LinkDescriptor("b"))) - .document(this.operationBuilder.build()); - assertThat(this.generatedSnippets.links()) - .is(tableWithHeader("Relation", "Description").row("`a`", "one").row("`b`", "Link b")); + .document(operationBuilder.build()); + assertThat(snippets.links()) + .isTable((table) -> table.withHeader("Relation", "Description").row("`a`", "one").row("`b`", "Link b")); } - @Test - public void linksWithCustomAttributes() throws IOException { - TemplateResourceResolver resolver = mock(TemplateResourceResolver.class); - given(resolver.resolveTemplateResource("links")).willReturn(snippetResource("links-with-title")); + @RenderedSnippetTest + @SnippetTemplate(snippet = "links", template = "links-with-title") + void linksWithCustomAttributes(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException { new LinksSnippet(new StubLinkExtractor().withLinks(new Link("a", "alpha"), new Link("b", "bravo")), Arrays.asList(new LinkDescriptor("a").description("one"), new LinkDescriptor("b").description("two")), attributes(key("title").value("Title for the links"))) - .document(this.operationBuilder - .attribute(TemplateEngine.class.getName(), new MustacheTemplateEngine(resolver)) - .build()); - assertThat(this.generatedSnippets.links()).contains("Title for the links"); + .document(operationBuilder.build()); + assertThat(snippets.links()).contains("Title for the links"); } - @Test - public void linksWithCustomDescriptorAttributes() throws IOException { - TemplateResourceResolver resolver = mock(TemplateResourceResolver.class); - given(resolver.resolveTemplateResource("links")).willReturn(snippetResource("links-with-extra-column")); + @RenderedSnippetTest + @SnippetTemplate(snippet = "links", template = "links-with-extra-column") + void linksWithCustomDescriptorAttributes(OperationBuilder operationBuilder, AssertableSnippets snippets) + throws IOException { new LinksSnippet(new StubLinkExtractor().withLinks(new Link("a", "alpha"), new Link("b", "bravo")), Arrays.asList(new LinkDescriptor("a").description("one").attributes(key("foo").value("alpha")), new LinkDescriptor("b").description("two").attributes(key("foo").value("bravo")))) - .document(this.operationBuilder - .attribute(TemplateEngine.class.getName(), new MustacheTemplateEngine(resolver)) - .build()); - assertThat(this.generatedSnippets.links()) - .is(tableWithHeader("Relation", "Description", "Foo").row("a", "one", "alpha").row("b", "two", "bravo")); + .document(operationBuilder.build()); + assertThat(snippets.links()).isTable((table) -> table.withHeader("Relation", "Description", "Foo") + .row("a", "one", "alpha") + .row("b", "two", "bravo")); } - @Test - public void additionalDescriptors() throws IOException { + @RenderedSnippetTest + void additionalDescriptors(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException { HypermediaDocumentation .links(new StubLinkExtractor().withLinks(new Link("a", "alpha"), new Link("b", "bravo")), new LinkDescriptor("a").description("one")) .and(new LinkDescriptor("b").description("two")) - .document(this.operationBuilder.build()); - assertThat(this.generatedSnippets.links()) - .is(tableWithHeader("Relation", "Description").row("`a`", "one").row("`b`", "two")); + .document(operationBuilder.build()); + assertThat(snippets.links()) + .isTable((table) -> table.withHeader("Relation", "Description").row("`a`", "one").row("`b`", "two")); } - @Test - public void tableCellContentIsEscapedWhenNecessary() throws IOException { + @RenderedSnippetTest + void tableCellContentIsEscapedWhenNecessary(OperationBuilder operationBuilder, AssertableSnippets snippets) + throws IOException { new LinksSnippet(new StubLinkExtractor().withLinks(new Link("Foo|Bar", "foo")), Arrays.asList(new LinkDescriptor("Foo|Bar").description("one|two"))) - .document(this.operationBuilder.build()); - assertThat(this.generatedSnippets.links()).is(tableWithHeader("Relation", "Description") - .row(escapeIfNecessary("`Foo|Bar`"), escapeIfNecessary("one|two"))); + .document(operationBuilder.build()); + assertThat(snippets.links()) + .isTable((table) -> table.withHeader("Relation", "Description").row("`Foo|Bar`", "one|two")); + } + + @SnippetTest + void undocumentedLink(OperationBuilder operationBuilder) { + assertThatExceptionOfType(SnippetException.class) + .isThrownBy(() -> new LinksSnippet(new StubLinkExtractor().withLinks(new Link("foo", "bar")), + Collections.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]"); } - private String escapeIfNecessary(String input) { - if (this.templateFormat.getId().equals(TemplateFormats.markdown().getId())) { - return input; - } - return input.replace("|", "\\|"); + @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"); } } diff --git a/spring-restdocs-core/src/test/java/org/springframework/restdocs/operation/preprocess/ContentModifyingOperationPreprocessorTests.java b/spring-restdocs-core/src/test/java/org/springframework/restdocs/operation/preprocess/ContentModifyingOperationPreprocessorTests.java index 2d3e6255..d107fec0 100644 --- a/spring-restdocs-core/src/test/java/org/springframework/restdocs/operation/preprocess/ContentModifyingOperationPreprocessorTests.java +++ b/spring-restdocs-core/src/test/java/org/springframework/restdocs/operation/preprocess/ContentModifyingOperationPreprocessorTests.java @@ -19,7 +19,7 @@ import java.net.URI; import java.util.Collections; -import org.junit.Test; +import org.junit.jupiter.api.Test; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; @@ -39,7 +39,7 @@ * @author Andy Wilkinson * */ -public class ContentModifyingOperationPreprocessorTests { +class ContentModifyingOperationPreprocessorTests { private final OperationRequestFactory requestFactory = new OperationRequestFactory(); @@ -56,7 +56,7 @@ public byte[] modifyContent(byte[] originalContent, MediaType mediaType) { }); @Test - public void modifyRequestContent() { + void modifyRequestContent() { OperationRequest request = this.requestFactory.create(URI.create("http://localhost"), HttpMethod.GET, "content".getBytes(), new HttpHeaders(), Collections.emptyList()); OperationRequest preprocessed = this.preprocessor.preprocess(request); @@ -64,7 +64,7 @@ public void modifyRequestContent() { } @Test - public void modifyResponseContent() { + void modifyResponseContent() { OperationResponse response = this.responseFactory.create(HttpStatus.OK, new HttpHeaders(), "content".getBytes()); OperationResponse preprocessed = this.preprocessor.preprocess(response); @@ -72,7 +72,7 @@ public void modifyResponseContent() { } @Test - public void contentLengthIsUpdated() { + void contentLengthIsUpdated() { HttpHeaders httpHeaders = new HttpHeaders(); httpHeaders.setContentLength(7); OperationRequest request = this.requestFactory.create(URI.create("http://localhost"), HttpMethod.GET, diff --git a/spring-restdocs-core/src/test/java/org/springframework/restdocs/operation/preprocess/DelegatingOperationRequestPreprocessorTests.java b/spring-restdocs-core/src/test/java/org/springframework/restdocs/operation/preprocess/DelegatingOperationRequestPreprocessorTests.java index d80c7071..8727e5ec 100644 --- a/spring-restdocs-core/src/test/java/org/springframework/restdocs/operation/preprocess/DelegatingOperationRequestPreprocessorTests.java +++ b/spring-restdocs-core/src/test/java/org/springframework/restdocs/operation/preprocess/DelegatingOperationRequestPreprocessorTests.java @@ -18,7 +18,7 @@ import java.util.Arrays; -import org.junit.Test; +import org.junit.jupiter.api.Test; import org.springframework.restdocs.operation.OperationRequest; @@ -31,10 +31,10 @@ * * @author Andy Wilkinson */ -public class DelegatingOperationRequestPreprocessorTests { +class DelegatingOperationRequestPreprocessorTests { @Test - public void delegationOccurs() { + void delegationOccurs() { OperationRequest originalRequest = mock(OperationRequest.class); OperationPreprocessor preprocessor1 = mock(OperationPreprocessor.class); OperationRequest preprocessedRequest1 = mock(OperationRequest.class); diff --git a/spring-restdocs-core/src/test/java/org/springframework/restdocs/operation/preprocess/DelegatingOperationResponsePreprocessorTests.java b/spring-restdocs-core/src/test/java/org/springframework/restdocs/operation/preprocess/DelegatingOperationResponsePreprocessorTests.java index 1058db27..e63185c5 100644 --- a/spring-restdocs-core/src/test/java/org/springframework/restdocs/operation/preprocess/DelegatingOperationResponsePreprocessorTests.java +++ b/spring-restdocs-core/src/test/java/org/springframework/restdocs/operation/preprocess/DelegatingOperationResponsePreprocessorTests.java @@ -18,7 +18,7 @@ import java.util.Arrays; -import org.junit.Test; +import org.junit.jupiter.api.Test; import org.springframework.restdocs.operation.OperationResponse; @@ -31,10 +31,10 @@ * * @author Andy Wilkinson */ -public class DelegatingOperationResponsePreprocessorTests { +class DelegatingOperationResponsePreprocessorTests { @Test - public void delegationOccurs() { + void delegationOccurs() { OperationResponse originalResponse = mock(OperationResponse.class); OperationPreprocessor preprocessor1 = mock(OperationPreprocessor.class); OperationResponse preprocessedResponse1 = mock(OperationResponse.class); diff --git a/spring-restdocs-core/src/test/java/org/springframework/restdocs/operation/preprocess/HeadersModifyingOperationPreprocessorTests.java b/spring-restdocs-core/src/test/java/org/springframework/restdocs/operation/preprocess/HeadersModifyingOperationPreprocessorTests.java index 6aa63b02..3ae96de2 100644 --- a/spring-restdocs-core/src/test/java/org/springframework/restdocs/operation/preprocess/HeadersModifyingOperationPreprocessorTests.java +++ b/spring-restdocs-core/src/test/java/org/springframework/restdocs/operation/preprocess/HeadersModifyingOperationPreprocessorTests.java @@ -22,7 +22,7 @@ import java.util.List; import java.util.function.Consumer; -import org.junit.Test; +import org.junit.jupiter.api.Test; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; @@ -33,6 +33,7 @@ import org.springframework.restdocs.operation.OperationResponseFactory; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.entry; /** * Tests for {@link HeadersModifyingOperationPreprocessor}. @@ -40,92 +41,102 @@ * @author Jihoon Cha * @author Andy Wilkinson */ -public class HeadersModifyingOperationPreprocessorTests { +class HeadersModifyingOperationPreprocessorTests { private final HeadersModifyingOperationPreprocessor preprocessor = new HeadersModifyingOperationPreprocessor(); @Test - public void addNewHeader() { + void addNewHeader() { this.preprocessor.add("a", "alpha"); - assertThat(this.preprocessor.preprocess(createRequest()).getHeaders()).containsEntry("a", - Arrays.asList("alpha")); - assertThat(this.preprocessor.preprocess(createResponse()).getHeaders()).containsEntry("a", - Arrays.asList("alpha")); + assertThat(this.preprocessor.preprocess(createRequest()).getHeaders().get("a")) + .isEqualTo(Arrays.asList("alpha")); + assertThat(this.preprocessor.preprocess(createResponse()).getHeaders().get("a")) + .isEqualTo(Arrays.asList("alpha")); } @Test - public void addValueToExistingHeader() { + void addValueToExistingHeader() { this.preprocessor.add("a", "alpha"); - assertThat(this.preprocessor.preprocess(createRequest((headers) -> headers.add("a", "apple"))).getHeaders()) - .containsEntry("a", Arrays.asList("apple", "alpha")); - assertThat(this.preprocessor.preprocess(createResponse((headers) -> headers.add("a", "apple"))).getHeaders()) - .containsEntry("a", Arrays.asList("apple", "alpha")); + assertThat(this.preprocessor.preprocess(createRequest((headers) -> headers.add("a", "apple"))) + .getHeaders() + .headerSet()).contains(entry("a", Arrays.asList("apple", "alpha"))); + assertThat(this.preprocessor.preprocess(createResponse((headers) -> headers.add("a", "apple"))) + .getHeaders() + .headerSet()).contains(entry("a", Arrays.asList("apple", "alpha"))); } @Test - public void setNewHeader() { + void setNewHeader() { this.preprocessor.set("a", "alpha", "avocado"); - assertThat(this.preprocessor.preprocess(createRequest()).getHeaders()).containsEntry("a", - Arrays.asList("alpha", "avocado")); - assertThat(this.preprocessor.preprocess(createResponse()).getHeaders()).containsEntry("a", - Arrays.asList("alpha", "avocado")); + assertThat(this.preprocessor.preprocess(createRequest()).getHeaders().headerSet()) + .contains(entry("a", Arrays.asList("alpha", "avocado"))); + assertThat(this.preprocessor.preprocess(createResponse()).getHeaders().headerSet()) + .contains(entry("a", Arrays.asList("alpha", "avocado"))); } @Test - public void setExistingHeader() { + void setExistingHeader() { this.preprocessor.set("a", "alpha", "avocado"); - assertThat(this.preprocessor.preprocess(createRequest((headers) -> headers.add("a", "apple"))).getHeaders()) - .containsEntry("a", Arrays.asList("alpha", "avocado")); - assertThat(this.preprocessor.preprocess(createResponse((headers) -> headers.add("a", "apple"))).getHeaders()) - .containsEntry("a", Arrays.asList("alpha", "avocado")); + assertThat(this.preprocessor.preprocess(createRequest((headers) -> headers.add("a", "apple"))) + .getHeaders() + .headerSet()).contains(entry("a", Arrays.asList("alpha", "avocado"))); + assertThat(this.preprocessor.preprocess(createResponse((headers) -> headers.add("a", "apple"))) + .getHeaders() + .headerSet()).contains(entry("a", Arrays.asList("alpha", "avocado"))); } @Test - public void removeNonExistentHeader() { + void removeNonExistentHeader() { this.preprocessor.remove("a"); - assertThat(this.preprocessor.preprocess(createRequest()).getHeaders()).doesNotContainKey("a"); - assertThat(this.preprocessor.preprocess(createResponse()).getHeaders()).doesNotContainKey("a"); + assertThat(this.preprocessor.preprocess(createRequest()).getHeaders().headerNames()).doesNotContain("a"); + assertThat(this.preprocessor.preprocess(createResponse()).getHeaders().headerNames()).doesNotContain("a"); } @Test - public void removeHeader() { + void removeHeader() { this.preprocessor.remove("a"); - assertThat(this.preprocessor.preprocess(createRequest((headers) -> headers.add("a", "apple"))).getHeaders()) - .doesNotContainKey("a"); - assertThat(this.preprocessor.preprocess(createResponse((headers) -> headers.add("a", "apple"))).getHeaders()) - .doesNotContainKey("a"); + assertThat(this.preprocessor.preprocess(createRequest((headers) -> headers.add("a", "apple"))) + .getHeaders() + .headerNames()).doesNotContain("a"); + assertThat(this.preprocessor.preprocess(createResponse((headers) -> headers.add("a", "apple"))) + .getHeaders() + .headerNames()).doesNotContain("a"); } @Test - public void removeHeaderValueForNonExistentHeader() { + void removeHeaderValueForNonExistentHeader() { this.preprocessor.remove("a", "apple"); - assertThat(this.preprocessor.preprocess(createRequest()).getHeaders()).doesNotContainKey("a"); - assertThat(this.preprocessor.preprocess(createResponse()).getHeaders()).doesNotContainKey("a"); + assertThat(this.preprocessor.preprocess(createRequest()).getHeaders().headerNames()).doesNotContain("a"); + assertThat(this.preprocessor.preprocess(createResponse()).getHeaders().headerNames()).doesNotContain("a"); } @Test - public void removeHeaderValueWithMultipleValues() { + void removeHeaderValueWithMultipleValues() { this.preprocessor.remove("a", "apple"); assertThat( this.preprocessor.preprocess(createRequest((headers) -> headers.addAll("a", List.of("apple", "alpha")))) - .getHeaders()) - .containsEntry("a", Arrays.asList("alpha")); + .getHeaders() + .headerSet()) + .contains(entry("a", Arrays.asList("alpha"))); assertThat(this.preprocessor .preprocess(createResponse((headers) -> headers.addAll("a", List.of("apple", "alpha")))) - .getHeaders()).containsEntry("a", Arrays.asList("alpha")); + .getHeaders() + .headerSet()).contains(entry("a", Arrays.asList("alpha"))); } @Test - public void removeHeaderValueWithSingleValueRemovesEntryEntirely() { + void removeHeaderValueWithSingleValueRemovesEntryEntirely() { this.preprocessor.remove("a", "apple"); - assertThat(this.preprocessor.preprocess(createRequest((headers) -> headers.add("a", "apple"))).getHeaders()) - .doesNotContainKey("a"); - assertThat(this.preprocessor.preprocess(createResponse((headers) -> headers.add("a", "apple"))).getHeaders()) - .doesNotContainKey("a"); + assertThat(this.preprocessor.preprocess(createRequest((headers) -> headers.add("a", "apple"))) + .getHeaders() + .headerNames()).doesNotContain("a"); + assertThat(this.preprocessor.preprocess(createResponse((headers) -> headers.add("a", "apple"))) + .getHeaders() + .headerNames()).doesNotContain("a"); } @Test - public void removeHeadersByNamePattern() { + void removeHeadersByNamePattern() { Consumer headersCustomizer = (headers) -> { headers.add("apple", "apple"); headers.add("alpha", "alpha"); @@ -133,10 +144,10 @@ public void removeHeadersByNamePattern() { headers.add("bravo", "bravo"); }; this.preprocessor.removeMatching("^a.*"); - assertThat(this.preprocessor.preprocess(createRequest(headersCustomizer)).getHeaders()).containsOnlyKeys("Host", - "bravo"); - assertThat(this.preprocessor.preprocess(createResponse(headersCustomizer)).getHeaders()) - .containsOnlyKeys("bravo"); + assertThat(this.preprocessor.preprocess(createRequest(headersCustomizer)).getHeaders().headerNames()) + .containsOnly("Host", "bravo"); + assertThat(this.preprocessor.preprocess(createResponse(headersCustomizer)).getHeaders().headerNames()) + .containsOnly("bravo"); } private OperationRequest createRequest() { diff --git a/spring-restdocs-core/src/test/java/org/springframework/restdocs/operation/preprocess/LinkMaskingContentModifierTests.java b/spring-restdocs-core/src/test/java/org/springframework/restdocs/operation/preprocess/LinkMaskingContentModifierTests.java index b2335c4c..3e198d54 100644 --- a/spring-restdocs-core/src/test/java/org/springframework/restdocs/operation/preprocess/LinkMaskingContentModifierTests.java +++ b/spring-restdocs-core/src/test/java/org/springframework/restdocs/operation/preprocess/LinkMaskingContentModifierTests.java @@ -23,10 +23,11 @@ import java.util.Map; import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.SerializationFeature; -import org.junit.Test; +import org.junit.jupiter.api.Test; +import tools.jackson.core.JacksonException; +import tools.jackson.databind.ObjectMapper; +import tools.jackson.databind.SerializationFeature; +import tools.jackson.databind.json.JsonMapper; import org.springframework.restdocs.hypermedia.Link; @@ -38,7 +39,7 @@ * @author Andy Wilkinson * */ -public class LinkMaskingContentModifierTests { +class LinkMaskingContentModifierTests { private final ContentModifier contentModifier = new LinkMaskingContentModifier(); @@ -47,50 +48,52 @@ public class LinkMaskingContentModifierTests { private final Link[] maskedLinks = new Link[] { new Link("a", "..."), new Link("b", "...") }; @Test - public void halLinksAreMasked() throws Exception { + void halLinksAreMasked() throws Exception { assertThat(this.contentModifier.modifyContent(halPayloadWithLinks(this.links), null)) .isEqualTo(halPayloadWithLinks(this.maskedLinks)); } @Test - public void formattedHalLinksAreMasked() throws Exception { + void formattedHalLinksAreMasked() throws Exception { assertThat(this.contentModifier.modifyContent(formattedHalPayloadWithLinks(this.links), null)) .isEqualTo(formattedHalPayloadWithLinks(this.maskedLinks)); } @Test - public void atomLinksAreMasked() throws Exception { + void atomLinksAreMasked() throws Exception { assertThat(this.contentModifier.modifyContent(atomPayloadWithLinks(this.links), null)) .isEqualTo(atomPayloadWithLinks(this.maskedLinks)); } @Test - public void formattedAtomLinksAreMasked() throws Exception { + void formattedAtomLinksAreMasked() throws Exception { assertThat(this.contentModifier.modifyContent(formattedAtomPayloadWithLinks(this.links), null)) .isEqualTo(formattedAtomPayloadWithLinks(this.maskedLinks)); } @Test - public void maskCanBeCustomized() throws Exception { + void maskCanBeCustomized() throws Exception { assertThat( new LinkMaskingContentModifier("custom").modifyContent(formattedAtomPayloadWithLinks(this.links), null)) .isEqualTo(formattedAtomPayloadWithLinks(new Link("a", "custom"), new Link("b", "custom"))); } @Test - public void maskCanUseUtf8Characters() throws Exception { + void maskCanUseUtf8Characters() throws Exception { String ellipsis = "\u2026"; assertThat( new LinkMaskingContentModifier(ellipsis).modifyContent(formattedHalPayloadWithLinks(this.links), null)) .isEqualTo(formattedHalPayloadWithLinks(new Link("a", ellipsis), new Link("b", ellipsis))); } - private byte[] atomPayloadWithLinks(Link... links) throws JsonProcessingException { + private byte[] atomPayloadWithLinks(Link... links) throws JacksonException { return new ObjectMapper().writeValueAsBytes(createAtomPayload(links)); } - private byte[] formattedAtomPayloadWithLinks(Link... links) throws JsonProcessingException { - return new ObjectMapper().configure(SerializationFeature.INDENT_OUTPUT, true) + private byte[] formattedAtomPayloadWithLinks(Link... links) throws JacksonException { + return JsonMapper.builder() + .enable(SerializationFeature.INDENT_OUTPUT) + .build() .writeValueAsBytes(createAtomPayload(links)); } @@ -100,12 +103,14 @@ private AtomPayload createAtomPayload(Link... links) { return payload; } - private byte[] halPayloadWithLinks(Link... links) throws JsonProcessingException { + private byte[] halPayloadWithLinks(Link... links) throws JacksonException { return new ObjectMapper().writeValueAsBytes(createHalPayload(links)); } - private byte[] formattedHalPayloadWithLinks(Link... links) throws JsonProcessingException { - return new ObjectMapper().configure(SerializationFeature.INDENT_OUTPUT, true) + private byte[] formattedHalPayloadWithLinks(Link... links) throws JacksonException { + return JsonMapper.builder() + .enable(SerializationFeature.INDENT_OUTPUT) + .build() .writeValueAsBytes(createHalPayload(links)); } diff --git a/spring-restdocs-core/src/test/java/org/springframework/restdocs/operation/preprocess/PatternReplacingContentModifierTests.java b/spring-restdocs-core/src/test/java/org/springframework/restdocs/operation/preprocess/PatternReplacingContentModifierTests.java index 40412b44..91766fe6 100644 --- a/spring-restdocs-core/src/test/java/org/springframework/restdocs/operation/preprocess/PatternReplacingContentModifierTests.java +++ b/spring-restdocs-core/src/test/java/org/springframework/restdocs/operation/preprocess/PatternReplacingContentModifierTests.java @@ -20,7 +20,7 @@ import java.nio.charset.StandardCharsets; import java.util.regex.Pattern; -import org.junit.Test; +import org.junit.jupiter.api.Test; import org.springframework.http.MediaType; @@ -31,10 +31,10 @@ * * @author Andy Wilkinson */ -public class PatternReplacingContentModifierTests { +class PatternReplacingContentModifierTests { @Test - public void patternsAreReplaced() { + void patternsAreReplaced() { Pattern pattern = Pattern.compile("[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}", Pattern.CASE_INSENSITIVE); PatternReplacingContentModifier contentModifier = new PatternReplacingContentModifier(pattern, "<>"); @@ -44,7 +44,7 @@ public void patternsAreReplaced() { } @Test - public void contentThatDoesNotMatchIsUnchanged() { + void contentThatDoesNotMatchIsUnchanged() { Pattern pattern = Pattern.compile("[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}", Pattern.CASE_INSENSITIVE); PatternReplacingContentModifier contentModifier = new PatternReplacingContentModifier(pattern, "<>"); @@ -53,7 +53,7 @@ public void contentThatDoesNotMatchIsUnchanged() { } @Test - public void encodingIsPreservedUsingCharsetFromContentType() { + void encodingIsPreservedUsingCharsetFromContentType() { String japaneseContent = "\u30b3\u30f3\u30c6\u30f3\u30c4"; Pattern pattern = Pattern.compile("[0-9]+"); PatternReplacingContentModifier contentModifier = new PatternReplacingContentModifier(pattern, "<>"); @@ -63,7 +63,7 @@ public void encodingIsPreservedUsingCharsetFromContentType() { } @Test - public void encodingIsPreservedUsingFallbackCharset() { + void encodingIsPreservedUsingFallbackCharset() { String japaneseContent = "\u30b3\u30f3\u30c6\u30f3\u30c4"; Pattern pattern = Pattern.compile("[0-9]+"); PatternReplacingContentModifier contentModifier = new PatternReplacingContentModifier(pattern, "<>", diff --git a/spring-restdocs-core/src/test/java/org/springframework/restdocs/operation/preprocess/PrettyPrintingContentModifierTests.java b/spring-restdocs-core/src/test/java/org/springframework/restdocs/operation/preprocess/PrettyPrintingContentModifierTests.java index d8fdeaf0..c02723d6 100644 --- a/spring-restdocs-core/src/test/java/org/springframework/restdocs/operation/preprocess/PrettyPrintingContentModifierTests.java +++ b/spring-restdocs-core/src/test/java/org/springframework/restdocs/operation/preprocess/PrettyPrintingContentModifierTests.java @@ -19,11 +19,12 @@ import java.util.HashMap; import java.util.Map; -import com.fasterxml.jackson.databind.ObjectMapper; -import org.junit.Rule; -import org.junit.Test; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import tools.jackson.databind.ObjectMapper; -import org.springframework.restdocs.testfixtures.OutputCaptureRule; +import org.springframework.restdocs.testfixtures.jupiter.CapturedOutput; +import org.springframework.restdocs.testfixtures.jupiter.OutputCaptureExtension; import static org.assertj.core.api.Assertions.assertThat; @@ -33,19 +34,17 @@ * @author Andy Wilkinson * */ -public class PrettyPrintingContentModifierTests { - - @Rule - public OutputCaptureRule outputCapture = new OutputCaptureRule(); +@ExtendWith(OutputCaptureExtension.class) +class PrettyPrintingContentModifierTests { @Test - public void prettyPrintJson() { + void prettyPrintJson() { assertThat(new PrettyPrintingContentModifier().modifyContent("{\"a\":5}".getBytes(), null)) .isEqualTo(String.format("{%n \"a\" : 5%n}").getBytes()); } @Test - public void prettyPrintXml() { + void prettyPrintXml() { assertThat(new PrettyPrintingContentModifier() .modifyContent("".getBytes(), null)) .isEqualTo(String @@ -55,28 +54,28 @@ public void prettyPrintXml() { } @Test - public void empytContentIsHandledGracefully() { + void empytContentIsHandledGracefully() { assertThat(new PrettyPrintingContentModifier().modifyContent("".getBytes(), null)).isEqualTo("".getBytes()); } @Test - public void nonJsonAndNonXmlContentIsHandledGracefully() { + void nonJsonAndNonXmlContentIsHandledGracefully(CapturedOutput output) { String content = "abcdefg"; assertThat(new PrettyPrintingContentModifier().modifyContent(content.getBytes(), null)) .isEqualTo(content.getBytes()); - assertThat(this.outputCapture).isEmpty(); + assertThat(output).isEmpty(); } @Test - public void nonJsonContentThatInitiallyLooksLikeJsonIsHandledGracefully() { + void nonJsonContentThatInitiallyLooksLikeJsonIsHandledGracefully(CapturedOutput output) { String content = "\"abc\",\"def\""; assertThat(new PrettyPrintingContentModifier().modifyContent(content.getBytes(), null)) .isEqualTo(content.getBytes()); - assertThat(this.outputCapture).isEmpty(); + assertThat(output).isEmpty(); } @Test - public void encodingIsPreserved() throws Exception { + void encodingIsPreserved() throws Exception { Map input = new HashMap<>(); input.put("japanese", "\u30b3\u30f3\u30c6\u30f3\u30c4"); ObjectMapper objectMapper = new ObjectMapper(); diff --git a/spring-restdocs-core/src/test/java/org/springframework/restdocs/operation/preprocess/UriModifyingOperationPreprocessorTests.java b/spring-restdocs-core/src/test/java/org/springframework/restdocs/operation/preprocess/UriModifyingOperationPreprocessorTests.java index cbe49e4b..cc5dfce7 100644 --- a/spring-restdocs-core/src/test/java/org/springframework/restdocs/operation/preprocess/UriModifyingOperationPreprocessorTests.java +++ b/spring-restdocs-core/src/test/java/org/springframework/restdocs/operation/preprocess/UriModifyingOperationPreprocessorTests.java @@ -21,7 +21,7 @@ import java.util.Collections; import java.util.List; -import org.junit.Test; +import org.junit.jupiter.api.Test; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; @@ -41,7 +41,7 @@ * * @author Andy Wilkinson */ -public class UriModifyingOperationPreprocessorTests { +class UriModifyingOperationPreprocessorTests { private final OperationRequestFactory requestFactory = new OperationRequestFactory(); @@ -50,14 +50,14 @@ public class UriModifyingOperationPreprocessorTests { private final UriModifyingOperationPreprocessor preprocessor = new UriModifyingOperationPreprocessor(); @Test - public void requestUriSchemeCanBeModified() { + void requestUriSchemeCanBeModified() { this.preprocessor.scheme("https"); OperationRequest processed = this.preprocessor.preprocess(createRequestWithUri("http://localhost:12345")); assertThat(processed.getUri()).isEqualTo(URI.create("https://localhost:12345")); } @Test - public void requestUriHostCanBeModified() { + void requestUriHostCanBeModified() { this.preprocessor.host("api.example.com"); OperationRequest processed = this.preprocessor.preprocess(createRequestWithUri("https://api.foo.com:12345")); assertThat(processed.getUri()).isEqualTo(URI.create("https://api.example.com:12345")); @@ -65,7 +65,7 @@ public void requestUriHostCanBeModified() { } @Test - public void requestUriPortCanBeModified() { + void requestUriPortCanBeModified() { this.preprocessor.port(23456); OperationRequest processed = this.preprocessor .preprocess(createRequestWithUri("https://api.example.com:12345")); @@ -74,7 +74,7 @@ public void requestUriPortCanBeModified() { } @Test - public void requestUriPortCanBeRemoved() { + void requestUriPortCanBeRemoved() { this.preprocessor.removePort(); OperationRequest processed = this.preprocessor .preprocess(createRequestWithUri("https://api.example.com:12345")); @@ -83,7 +83,7 @@ public void requestUriPortCanBeRemoved() { } @Test - public void requestUriPathIsPreserved() { + void requestUriPathIsPreserved() { this.preprocessor.removePort(); OperationRequest processed = this.preprocessor .preprocess(createRequestWithUri("https://api.example.com:12345/foo/bar")); @@ -91,7 +91,7 @@ public void requestUriPathIsPreserved() { } @Test - public void requestUriQueryIsPreserved() { + void requestUriQueryIsPreserved() { this.preprocessor.removePort(); OperationRequest processed = this.preprocessor .preprocess(createRequestWithUri("https://api.example.com:12345?foo=bar")); @@ -99,7 +99,7 @@ public void requestUriQueryIsPreserved() { } @Test - public void requestUriAnchorIsPreserved() { + void requestUriAnchorIsPreserved() { this.preprocessor.removePort(); OperationRequest processed = this.preprocessor .preprocess(createRequestWithUri("https://api.example.com:12345#foo")); @@ -107,7 +107,7 @@ public void requestUriAnchorIsPreserved() { } @Test - public void requestContentUriSchemeCanBeModified() { + void requestContentUriSchemeCanBeModified() { this.preprocessor.scheme("https"); OperationRequest processed = this.preprocessor.preprocess(createRequestWithContent( "The uri 'https://localhost:12345' should be used. foo:bar will be unaffected")); @@ -116,7 +116,7 @@ public void requestContentUriSchemeCanBeModified() { } @Test - public void requestContentUriHostCanBeModified() { + void requestContentUriHostCanBeModified() { this.preprocessor.host("api.example.com"); OperationRequest processed = this.preprocessor.preprocess(createRequestWithContent( "The uri 'https://localhost:12345' should be used. foo:bar will be unaffected")); @@ -125,7 +125,7 @@ public void requestContentUriHostCanBeModified() { } @Test - public void requestContentHostOfUriWithoutPortCanBeModified() { + void requestContentHostOfUriWithoutPortCanBeModified() { this.preprocessor.host("api.example.com"); OperationRequest processed = this.preprocessor.preprocess( createRequestWithContent("The uri 'https://localhost' should be used. foo:bar will be unaffected")); @@ -134,7 +134,7 @@ public void requestContentHostOfUriWithoutPortCanBeModified() { } @Test - public void requestContentUriPortCanBeAdded() { + void requestContentUriPortCanBeAdded() { this.preprocessor.port(23456); OperationRequest processed = this.preprocessor.preprocess( createRequestWithContent("The uri 'http://localhost' should be used. foo:bar will be unaffected")); @@ -143,7 +143,7 @@ public void requestContentUriPortCanBeAdded() { } @Test - public void requestContentUriPortCanBeModified() { + void requestContentUriPortCanBeModified() { this.preprocessor.port(23456); OperationRequest processed = this.preprocessor.preprocess(createRequestWithContent( "The uri 'http://localhost:12345' should be used. foo:bar will be unaffected")); @@ -152,7 +152,7 @@ public void requestContentUriPortCanBeModified() { } @Test - public void requestContentUriPortCanBeRemoved() { + void requestContentUriPortCanBeRemoved() { this.preprocessor.removePort(); OperationRequest processed = this.preprocessor.preprocess(createRequestWithContent( "The uri 'http://localhost:12345' should be used. foo:bar will be unaffected")); @@ -161,7 +161,7 @@ public void requestContentUriPortCanBeRemoved() { } @Test - public void multipleRequestContentUrisCanBeModified() { + void multipleRequestContentUrisCanBeModified() { this.preprocessor.removePort(); OperationRequest processed = this.preprocessor.preprocess(createRequestWithContent( "Use 'http://localhost:12345' or 'https://localhost:23456' to access the service")); @@ -170,7 +170,7 @@ public void multipleRequestContentUrisCanBeModified() { } @Test - public void requestContentUriPathIsPreserved() { + void requestContentUriPathIsPreserved() { this.preprocessor.removePort(); OperationRequest processed = this.preprocessor .preprocess(createRequestWithContent("The uri 'http://localhost:12345/foo/bar' should be used")); @@ -178,7 +178,7 @@ public void requestContentUriPathIsPreserved() { } @Test - public void requestContentUriQueryIsPreserved() { + void requestContentUriQueryIsPreserved() { this.preprocessor.removePort(); OperationRequest processed = this.preprocessor .preprocess(createRequestWithContent("The uri 'http://localhost:12345?foo=bar' should be used")); @@ -186,7 +186,7 @@ public void requestContentUriQueryIsPreserved() { } @Test - public void requestContentUriAnchorIsPreserved() { + void requestContentUriAnchorIsPreserved() { this.preprocessor.removePort(); OperationRequest processed = this.preprocessor .preprocess(createRequestWithContent("The uri 'http://localhost:12345#foo' should be used")); @@ -194,7 +194,7 @@ public void requestContentUriAnchorIsPreserved() { } @Test - public void responseContentUriSchemeCanBeModified() { + void responseContentUriSchemeCanBeModified() { this.preprocessor.scheme("https"); OperationResponse processed = this.preprocessor .preprocess(createResponseWithContent("The uri 'http://localhost:12345' should be used")); @@ -202,7 +202,7 @@ public void responseContentUriSchemeCanBeModified() { } @Test - public void responseContentUriHostCanBeModified() { + void responseContentUriHostCanBeModified() { this.preprocessor.host("api.example.com"); OperationResponse processed = this.preprocessor .preprocess(createResponseWithContent("The uri 'https://localhost:12345' should be used")); @@ -211,7 +211,7 @@ public void responseContentUriHostCanBeModified() { } @Test - public void responseContentUriPortCanBeModified() { + void responseContentUriPortCanBeModified() { this.preprocessor.port(23456); OperationResponse processed = this.preprocessor .preprocess(createResponseWithContent("The uri 'http://localhost:12345' should be used")); @@ -219,7 +219,7 @@ public void responseContentUriPortCanBeModified() { } @Test - public void responseContentUriPortCanBeRemoved() { + void responseContentUriPortCanBeRemoved() { this.preprocessor.removePort(); OperationResponse processed = this.preprocessor .preprocess(createResponseWithContent("The uri 'http://localhost:12345' should be used")); @@ -227,7 +227,7 @@ public void responseContentUriPortCanBeRemoved() { } @Test - public void multipleResponseContentUrisCanBeModified() { + void multipleResponseContentUrisCanBeModified() { this.preprocessor.removePort(); OperationResponse processed = this.preprocessor.preprocess(createResponseWithContent( "Use 'http://localhost:12345' or 'https://localhost:23456' to access the service")); @@ -236,7 +236,7 @@ public void multipleResponseContentUrisCanBeModified() { } @Test - public void responseContentUriPathIsPreserved() { + void responseContentUriPathIsPreserved() { this.preprocessor.removePort(); OperationResponse processed = this.preprocessor .preprocess(createResponseWithContent("The uri 'http://localhost:12345/foo/bar' should be used")); @@ -244,7 +244,7 @@ public void responseContentUriPathIsPreserved() { } @Test - public void responseContentUriQueryIsPreserved() { + void responseContentUriQueryIsPreserved() { this.preprocessor.removePort(); OperationResponse processed = this.preprocessor .preprocess(createResponseWithContent("The uri 'http://localhost:12345?foo=bar' should be used")); @@ -252,7 +252,7 @@ public void responseContentUriQueryIsPreserved() { } @Test - public void responseContentUriAnchorIsPreserved() { + void responseContentUriAnchorIsPreserved() { this.preprocessor.removePort(); OperationResponse processed = this.preprocessor .preprocess(createResponseWithContent("The uri 'http://localhost:12345#foo' should be used")); @@ -260,7 +260,7 @@ public void responseContentUriAnchorIsPreserved() { } @Test - public void urisInRequestHeadersCanBeModified() { + void urisInRequestHeadersCanBeModified() { OperationRequest processed = this.preprocessor.host("api.example.com") .preprocess(createRequestWithHeader("Foo", "https://locahost:12345")); assertThat(processed.getHeaders().getFirst("Foo")).isEqualTo("https://api.example.com:12345"); @@ -268,14 +268,14 @@ public void urisInRequestHeadersCanBeModified() { } @Test - public void urisInResponseHeadersCanBeModified() { + void urisInResponseHeadersCanBeModified() { OperationResponse processed = this.preprocessor.host("api.example.com") .preprocess(createResponseWithHeader("Foo", "https://locahost:12345")); assertThat(processed.getHeaders().getFirst("Foo")).isEqualTo("https://api.example.com:12345"); } @Test - public void urisInRequestPartHeadersCanBeModified() { + void urisInRequestPartHeadersCanBeModified() { OperationRequest processed = this.preprocessor.host("api.example.com") .preprocess(createRequestWithPartWithHeader("Foo", "https://locahost:12345")); assertThat(processed.getParts().iterator().next().getHeaders().getFirst("Foo")) @@ -283,7 +283,7 @@ public void urisInRequestPartHeadersCanBeModified() { } @Test - public void urisInRequestPartContentCanBeModified() { + void urisInRequestPartContentCanBeModified() { OperationRequest processed = this.preprocessor.host("api.example.com") .preprocess(createRequestWithPartWithContent("The uri 'https://localhost:12345' should be used")); assertThat(new String(processed.getParts().iterator().next().getContent())) @@ -291,7 +291,7 @@ public void urisInRequestPartContentCanBeModified() { } @Test - public void modifiedUriDoesNotGetDoubleEncoded() { + void modifiedUriDoesNotGetDoubleEncoded() { this.preprocessor.scheme("https"); OperationRequest processed = this.preprocessor .preprocess(createRequestWithUri("http://localhost:12345?foo=%7B%7D")); @@ -300,7 +300,7 @@ public void modifiedUriDoesNotGetDoubleEncoded() { } @Test - public void resultingRequestHasCookiesFromOriginalRequst() { + void resultingRequestHasCookiesFromOriginalRequst() { List cookies = Arrays.asList(new RequestCookie("a", "alpha")); OperationRequest request = this.requestFactory.create(URI.create("http://localhost:12345"), HttpMethod.GET, new byte[0], new HttpHeaders(), Collections.emptyList(), cookies); diff --git a/spring-restdocs-core/src/test/java/org/springframework/restdocs/payload/AsciidoctorRequestFieldsSnippetTests.java b/spring-restdocs-core/src/test/java/org/springframework/restdocs/payload/AsciidoctorRequestFieldsSnippetTests.java index eedd3c7a..671b2ef6 100644 --- a/spring-restdocs-core/src/test/java/org/springframework/restdocs/payload/AsciidoctorRequestFieldsSnippetTests.java +++ b/spring-restdocs-core/src/test/java/org/springframework/restdocs/payload/AsciidoctorRequestFieldsSnippetTests.java @@ -19,21 +19,13 @@ import java.io.IOException; import java.util.Arrays; -import org.junit.Rule; -import org.junit.Test; - -import org.springframework.core.io.FileSystemResource; -import org.springframework.restdocs.templates.TemplateEngine; -import org.springframework.restdocs.templates.TemplateFormats; -import org.springframework.restdocs.templates.TemplateResourceResolver; -import org.springframework.restdocs.templates.mustache.MustacheTemplateEngine; -import org.springframework.restdocs.testfixtures.GeneratedSnippets; -import org.springframework.restdocs.testfixtures.OperationBuilder; -import org.springframework.restdocs.testfixtures.SnippetConditions; +import org.springframework.restdocs.testfixtures.jupiter.AssertableSnippets; +import org.springframework.restdocs.testfixtures.jupiter.OperationBuilder; +import org.springframework.restdocs.testfixtures.jupiter.RenderedSnippetTest; +import org.springframework.restdocs.testfixtures.jupiter.RenderedSnippetTest.Format; +import org.springframework.restdocs.testfixtures.jupiter.SnippetTemplate; import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.BDDMockito.given; -import static org.mockito.Mockito.mock; import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath; /** @@ -41,33 +33,17 @@ * * @author Andy Wilkinson */ -public class AsciidoctorRequestFieldsSnippetTests { - - @Rule - public OperationBuilder operationBuilder = new OperationBuilder(TemplateFormats.asciidoctor()); - - @Rule - public GeneratedSnippets generatedSnippets = new GeneratedSnippets(TemplateFormats.asciidoctor()); - - @Test - public void requestFieldsWithListDescription() throws IOException { - TemplateResourceResolver resolver = mock(TemplateResourceResolver.class); - given(resolver.resolveTemplateResource("request-fields")) - .willReturn(snippetResource("request-fields-with-list-description")); - new RequestFieldsSnippet(Arrays.asList(fieldWithPath("a").description(Arrays.asList("one", "two")))).document( - this.operationBuilder.attribute(TemplateEngine.class.getName(), new MustacheTemplateEngine(resolver)) - .request("http://localhost") - .content("{\"a\": \"foo\"}") - .build()); - assertThat(this.generatedSnippets.requestFields()) - .is(SnippetConditions.tableWithHeader(TemplateFormats.asciidoctor(), "Path", "Type", "Description") - // - .row("a", "String", String.format(" - one%n - two")) - .configuration("[cols=\"1,1,1a\"]")); - } - - private FileSystemResource snippetResource(String name) { - return new FileSystemResource("src/test/resources/custom-snippet-templates/asciidoctor/" + name + ".snippet"); +class AsciidoctorRequestFieldsSnippetTests { + + @RenderedSnippetTest(format = Format.ASCIIDOCTOR) + @SnippetTemplate(snippet = "request-fields", template = "request-fields-with-list-description") + void requestFieldsWithListDescription(OperationBuilder operationBuilder, AssertableSnippets snippets) + throws IOException { + new RequestFieldsSnippet(Arrays.asList(fieldWithPath("a").description(Arrays.asList("one", "two")))) + .document(operationBuilder.request("http://localhost").content("{\"a\": \"foo\"}").build()); + assertThat(snippets.requestFields()).isTable((table) -> table.withHeader("Path", "Type", "Description") + .row("a", "String", String.format(" - one%n - two")) + .configuration("[cols=\"1,1,1a\"]")); } } diff --git a/spring-restdocs-core/src/test/java/org/springframework/restdocs/payload/FieldPathPayloadSubsectionExtractorTests.java b/spring-restdocs-core/src/test/java/org/springframework/restdocs/payload/FieldPathPayloadSubsectionExtractorTests.java index 8461a5af..2adf2352 100644 --- a/spring-restdocs-core/src/test/java/org/springframework/restdocs/payload/FieldPathPayloadSubsectionExtractorTests.java +++ b/spring-restdocs-core/src/test/java/org/springframework/restdocs/payload/FieldPathPayloadSubsectionExtractorTests.java @@ -16,16 +16,14 @@ package org.springframework.restdocs.payload; -import java.io.IOException; import java.util.Arrays; import java.util.Map; -import com.fasterxml.jackson.core.JsonParseException; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.JsonMappingException; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.SerializationFeature; -import org.junit.Test; +import org.junit.jupiter.api.Test; +import tools.jackson.core.JacksonException; +import tools.jackson.databind.ObjectMapper; +import tools.jackson.databind.SerializationFeature; +import tools.jackson.databind.json.JsonMapper; import org.springframework.http.MediaType; @@ -38,11 +36,11 @@ * * @author Andy Wilkinson */ -public class FieldPathPayloadSubsectionExtractorTests { +class FieldPathPayloadSubsectionExtractorTests { @Test @SuppressWarnings("unchecked") - public void extractMapSubsectionOfJsonMap() throws JsonParseException, JsonMappingException, IOException { + void extractMapSubsectionOfJsonMap() throws JacksonException { byte[] extractedPayload = new FieldPathPayloadSubsectionExtractor("a.b") .extractSubsection("{\"a\":{\"b\":{\"c\":5}}}".getBytes(), MediaType.APPLICATION_JSON); Map extracted = new ObjectMapper().readValue(extractedPayload, Map.class); @@ -52,8 +50,7 @@ public void extractMapSubsectionOfJsonMap() throws JsonParseException, JsonMappi @Test @SuppressWarnings("unchecked") - public void extractSingleElementArraySubsectionOfJsonMap() - throws JsonParseException, JsonMappingException, IOException { + void extractSingleElementArraySubsectionOfJsonMap() throws JacksonException { byte[] extractedPayload = new FieldPathPayloadSubsectionExtractor("a.[]") .extractSubsection("{\"a\":[{\"b\":5}]}".getBytes(), MediaType.APPLICATION_JSON); Map extracted = new ObjectMapper().readValue(extractedPayload, Map.class); @@ -63,8 +60,7 @@ public void extractSingleElementArraySubsectionOfJsonMap() @Test @SuppressWarnings("unchecked") - public void extractMultiElementArraySubsectionOfJsonMap() - throws JsonParseException, JsonMappingException, IOException { + void extractMultiElementArraySubsectionOfJsonMap() throws JacksonException { byte[] extractedPayload = new FieldPathPayloadSubsectionExtractor("a") .extractSubsection("{\"a\":[{\"b\":5},{\"b\":4}]}".getBytes(), MediaType.APPLICATION_JSON); Map extracted = new ObjectMapper().readValue(extractedPayload, Map.class); @@ -74,8 +70,7 @@ public void extractMultiElementArraySubsectionOfJsonMap() @Test @SuppressWarnings("unchecked") - public void extractMapSubsectionFromSingleElementArrayInAJsonMap() - throws JsonParseException, JsonMappingException, IOException { + void extractMapSubsectionFromSingleElementArrayInAJsonMap() throws JacksonException { byte[] extractedPayload = new FieldPathPayloadSubsectionExtractor("a.[].b") .extractSubsection("{\"a\":[{\"b\":{\"c\":5}}]}".getBytes(), MediaType.APPLICATION_JSON); Map extracted = new ObjectMapper().readValue(extractedPayload, Map.class); @@ -85,8 +80,7 @@ public void extractMapSubsectionFromSingleElementArrayInAJsonMap() @Test @SuppressWarnings("unchecked") - public void extractMapSubsectionWithCommonStructureFromMultiElementArrayInAJsonMap() - throws JsonParseException, JsonMappingException, IOException { + void extractMapSubsectionWithCommonStructureFromMultiElementArrayInAJsonMap() throws JacksonException { byte[] extractedPayload = new FieldPathPayloadSubsectionExtractor("a.[].b") .extractSubsection("{\"a\":[{\"b\":{\"c\":5}},{\"b\":{\"c\":6}}]}".getBytes(), MediaType.APPLICATION_JSON); Map extracted = new ObjectMapper().readValue(extractedPayload, Map.class); @@ -95,7 +89,7 @@ public void extractMapSubsectionWithCommonStructureFromMultiElementArrayInAJsonM } @Test - public void extractMapSubsectionWithVaryingStructureFromMultiElementArrayInAJsonMap() { + void extractMapSubsectionWithVaryingStructureFromMultiElementArrayInAJsonMap() { assertThatExceptionOfType(PayloadHandlingException.class) .isThrownBy(() -> new FieldPathPayloadSubsectionExtractor("a.[].b").extractSubsection( "{\"a\":[{\"b\":{\"c\":5}},{\"b\":{\"c\":6, \"d\": 7}}]}".getBytes(), MediaType.APPLICATION_JSON)) @@ -103,7 +97,7 @@ public void extractMapSubsectionWithVaryingStructureFromMultiElementArrayInAJson } @Test - public void extractMapSubsectionWithVaryingStructureFromInconsistentJsonMap() { + void extractMapSubsectionWithVaryingStructureFromInconsistentJsonMap() { assertThatExceptionOfType(PayloadHandlingException.class) .isThrownBy(() -> new FieldPathPayloadSubsectionExtractor("*.d").extractSubsection( "{\"a\":{\"b\":1},\"c\":{\"d\":{\"e\":1,\"f\":2}}}".getBytes(), MediaType.APPLICATION_JSON)) @@ -111,7 +105,7 @@ public void extractMapSubsectionWithVaryingStructureFromInconsistentJsonMap() { } @Test - public void extractMapSubsectionWithVaryingStructureFromInconsistentJsonMapWhereAllSubsectionFieldsAreOptional() { + void extractMapSubsectionWithVaryingStructureFromInconsistentJsonMapWhereAllSubsectionFieldsAreOptional() { assertThatExceptionOfType(PayloadHandlingException.class) .isThrownBy(() -> new FieldPathPayloadSubsectionExtractor("*.d").extractSubsection( "{\"a\":{\"b\":1},\"c\":{\"d\":{\"e\":1,\"f\":2}}}".getBytes(), MediaType.APPLICATION_JSON, @@ -121,8 +115,8 @@ public void extractMapSubsectionWithVaryingStructureFromInconsistentJsonMapWhere @Test @SuppressWarnings("unchecked") - public void extractMapSubsectionWithVaryingStructureDueToOptionalFieldsFromMultiElementArrayInAJsonMap() - throws JsonParseException, JsonMappingException, IOException { + void extractMapSubsectionWithVaryingStructureDueToOptionalFieldsFromMultiElementArrayInAJsonMap() + throws JacksonException { byte[] extractedPayload = new FieldPathPayloadSubsectionExtractor("a.[].b").extractSubsection( "{\"a\":[{\"b\":{\"c\":5}},{\"b\":{\"c\":6, \"d\": 7}}]}".getBytes(), MediaType.APPLICATION_JSON, Arrays.asList(new FieldDescriptor("d").optional())); @@ -133,8 +127,8 @@ public void extractMapSubsectionWithVaryingStructureDueToOptionalFieldsFromMulti @Test @SuppressWarnings("unchecked") - public void extractMapSubsectionWithVaryingStructureDueToOptionalParentFieldsFromMultiElementArrayInAJsonMap() - throws JsonParseException, JsonMappingException, IOException { + void extractMapSubsectionWithVaryingStructureDueToOptionalParentFieldsFromMultiElementArrayInAJsonMap() + throws JacksonException { byte[] extractedPayload = new FieldPathPayloadSubsectionExtractor("a.[].b").extractSubsection( "{\"a\":[{\"b\":{\"c\":5}},{\"b\":{\"c\":6, \"d\": { \"e\": 7}}}]}".getBytes(), MediaType.APPLICATION_JSON, Arrays.asList(new FieldDescriptor("d").optional())); @@ -144,9 +138,8 @@ public void extractMapSubsectionWithVaryingStructureDueToOptionalParentFieldsFro } @Test - public void extractedSubsectionIsPrettyPrintedWhenInputIsPrettyPrinted() - throws JsonParseException, JsonMappingException, JsonProcessingException, IOException { - ObjectMapper objectMapper = new ObjectMapper().enable(SerializationFeature.INDENT_OUTPUT); + void extractedSubsectionIsPrettyPrintedWhenInputIsPrettyPrinted() throws JacksonException { + ObjectMapper objectMapper = JsonMapper.builder().enable(SerializationFeature.INDENT_OUTPUT).build(); byte[] prettyPrintedPayload = objectMapper .writeValueAsBytes(objectMapper.readValue("{\"a\": { \"b\": { \"c\": 1 }}}", Object.class)); byte[] extractedSubsection = new FieldPathPayloadSubsectionExtractor("a.b") @@ -157,8 +150,7 @@ public void extractedSubsectionIsPrettyPrintedWhenInputIsPrettyPrinted() } @Test - public void extractedSubsectionIsNotPrettyPrintedWhenInputIsNotPrettyPrinted() - throws JsonParseException, JsonMappingException, JsonProcessingException, IOException { + void extractedSubsectionIsNotPrettyPrintedWhenInputIsNotPrettyPrinted() throws JacksonException { ObjectMapper objectMapper = new ObjectMapper(); byte[] payload = objectMapper .writeValueAsBytes(objectMapper.readValue("{\"a\": { \"b\": { \"c\": 1 }}}", Object.class)); @@ -169,7 +161,7 @@ public void extractedSubsectionIsNotPrettyPrintedWhenInputIsNotPrettyPrinted() } @Test - public void extractNonExistentSubsection() { + void extractNonExistentSubsection() { assertThatThrownBy(() -> new FieldPathPayloadSubsectionExtractor("a.c") .extractSubsection("{\"a\":{\"b\":{\"c\":5}}}".getBytes(), MediaType.APPLICATION_JSON)) .isInstanceOf(PayloadHandlingException.class) @@ -177,9 +169,9 @@ public void extractNonExistentSubsection() { } @Test - public void extractEmptyArraySubsection() { - assertThatThrownBy(() -> new FieldPathPayloadSubsectionExtractor("a") - .extractSubsection("{\"a\":[]}}".getBytes(), MediaType.APPLICATION_JSON)) + void extractEmptyArraySubsection() { + assertThatThrownBy(() -> new FieldPathPayloadSubsectionExtractor("a").extractSubsection("{\"a\":[]}".getBytes(), + MediaType.APPLICATION_JSON)) .isInstanceOf(PayloadHandlingException.class) .hasMessage("a identifies an empty section of the payload"); } diff --git a/spring-restdocs-core/src/test/java/org/springframework/restdocs/payload/FieldTypeResolverTests.java b/spring-restdocs-core/src/test/java/org/springframework/restdocs/payload/FieldTypeResolverTests.java index 5280b6f8..2c0935ad 100644 --- a/spring-restdocs-core/src/test/java/org/springframework/restdocs/payload/FieldTypeResolverTests.java +++ b/spring-restdocs-core/src/test/java/org/springframework/restdocs/payload/FieldTypeResolverTests.java @@ -18,7 +18,7 @@ import java.util.Collections; -import org.junit.Test; +import org.junit.jupiter.api.Test; import org.springframework.http.MediaType; diff --git a/spring-restdocs-core/src/test/java/org/springframework/restdocs/payload/JsonContentHandlerTests.java b/spring-restdocs-core/src/test/java/org/springframework/restdocs/payload/JsonContentHandlerTests.java index 6a7fe782..de61f3be 100644 --- a/spring-restdocs-core/src/test/java/org/springframework/restdocs/payload/JsonContentHandlerTests.java +++ b/spring-restdocs-core/src/test/java/org/springframework/restdocs/payload/JsonContentHandlerTests.java @@ -20,7 +20,7 @@ import java.util.Collections; import java.util.List; -import org.junit.Test; +import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; @@ -31,10 +31,10 @@ * @author Andy Wilkinson * @author Mathias Düsterhöft */ -public class JsonContentHandlerTests { +class JsonContentHandlerTests { @Test - public void typeForFieldWithNullValueMustMatch() { + void typeForFieldWithNullValueMustMatch() { FieldDescriptor descriptor = new FieldDescriptor("a").type(JsonFieldType.STRING); assertThatExceptionOfType(FieldTypesDoNotMatchException.class) .isThrownBy(() -> new JsonContentHandler("{\"a\": null}".getBytes(), Arrays.asList(descriptor)) @@ -42,7 +42,7 @@ public void typeForFieldWithNullValueMustMatch() { } @Test - public void typeForFieldWithNotNullAndThenNullValueMustMatch() { + void typeForFieldWithNotNullAndThenNullValueMustMatch() { FieldDescriptor descriptor = new FieldDescriptor("a[].id").type(JsonFieldType.STRING); assertThatExceptionOfType(FieldTypesDoNotMatchException.class).isThrownBy( () -> new JsonContentHandler("{\"a\":[{\"id\":1},{\"id\":null}]}".getBytes(), Arrays.asList(descriptor)) @@ -50,7 +50,7 @@ public void typeForFieldWithNotNullAndThenNullValueMustMatch() { } @Test - public void typeForFieldWithNullAndThenNotNullValueMustMatch() { + void typeForFieldWithNullAndThenNotNullValueMustMatch() { FieldDescriptor descriptor = new FieldDescriptor("a.[].id").type(JsonFieldType.STRING); assertThatExceptionOfType(FieldTypesDoNotMatchException.class).isThrownBy( () -> new JsonContentHandler("{\"a\":[{\"id\":null},{\"id\":1}]}".getBytes(), Arrays.asList(descriptor)) @@ -58,16 +58,16 @@ public void typeForFieldWithNullAndThenNotNullValueMustMatch() { } @Test - public void typeForOptionalFieldWithNumberAndThenNullValueIsNumber() { + void typeForOptionalFieldWithNumberAndThenNullValueIsNumber() { 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)) .resolveFieldType(descriptor); assertThat((JsonFieldType) fieldType).isEqualTo(JsonFieldType.NUMBER); } @Test - public void typeForOptionalFieldWithNullAndThenNumberIsNumber() { + void typeForOptionalFieldWithNullAndThenNumberIsNumber() { FieldDescriptor descriptor = new FieldDescriptor("a[].id").optional(); Object fieldType = new JsonContentHandler("{\"a\":[{\"id\":null},{\"id\":1}]}".getBytes(), Arrays.asList(descriptor)) @@ -76,16 +76,16 @@ public void typeForOptionalFieldWithNullAndThenNumberIsNumber() { } @Test - public void typeForFieldWithNumberAndThenNullValueIsVaries() { + void typeForFieldWithNumberAndThenNullValueIsVaries() { 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)) .resolveFieldType(descriptor); assertThat((JsonFieldType) fieldType).isEqualTo(JsonFieldType.VARIES); } @Test - public void typeForFieldWithNullAndThenNumberIsVaries() { + void typeForFieldWithNullAndThenNumberIsVaries() { FieldDescriptor descriptor = new FieldDescriptor("a[].id"); Object fieldType = new JsonContentHandler("{\"a\":[{\"id\":null},{\"id\":1}]}".getBytes(), Arrays.asList(descriptor)) @@ -94,7 +94,7 @@ public void typeForFieldWithNullAndThenNumberIsVaries() { } @Test - public void typeForOptionalFieldWithNullValueCanBeProvidedExplicitly() { + void typeForOptionalFieldWithNullValueCanBeProvidedExplicitly() { FieldDescriptor descriptor = new FieldDescriptor("a").type(JsonFieldType.STRING).optional(); Object fieldType = new JsonContentHandler("{\"a\": null}".getBytes(), Arrays.asList(descriptor)) .resolveFieldType(descriptor); @@ -102,7 +102,7 @@ public void typeForOptionalFieldWithNullValueCanBeProvidedExplicitly() { } @Test - public void typeForFieldWithSometimesPresentOptionalAncestorCanBeProvidedExplicitly() { + void typeForFieldWithSometimesPresentOptionalAncestorCanBeProvidedExplicitly() { FieldDescriptor descriptor = new FieldDescriptor("a.[].b.c").type(JsonFieldType.NUMBER); FieldDescriptor ancestor = new FieldDescriptor("a.[].b").optional(); Object fieldType = new JsonContentHandler("{\"a\":[ { \"d\": 4}, {\"b\":{\"c\":5}, \"d\": 4}]}".getBytes(), @@ -112,13 +112,13 @@ public void typeForFieldWithSometimesPresentOptionalAncestorCanBeProvidedExplici } @Test - public void failsFastWithNonJsonContent() { + void failsFastWithNonJsonContent() { assertThatExceptionOfType(PayloadHandlingException.class) .isThrownBy(() -> new JsonContentHandler("Non-JSON content".getBytes(), Collections.emptyList())); } @Test - public void describedFieldThatIsNotPresentIsConsideredMissing() { + void describedFieldThatIsNotPresentIsConsideredMissing() { List descriptors = Arrays.asList(new FieldDescriptor("a"), new FieldDescriptor("b"), new FieldDescriptor("c")); List missingFields = new JsonContentHandler("{\"a\": \"alpha\", \"b\":\"bravo\"}".getBytes(), @@ -129,7 +129,7 @@ public void describedFieldThatIsNotPresentIsConsideredMissing() { } @Test - public void describedOptionalFieldThatIsNotPresentIsNotConsideredMissing() { + void describedOptionalFieldThatIsNotPresentIsNotConsideredMissing() { List descriptors = Arrays.asList(new FieldDescriptor("a"), new FieldDescriptor("b"), new FieldDescriptor("c").optional()); List missingFields = new JsonContentHandler("{\"a\": \"alpha\", \"b\":\"bravo\"}".getBytes(), @@ -139,7 +139,7 @@ public void describedOptionalFieldThatIsNotPresentIsNotConsideredMissing() { } @Test - public void describedFieldThatIsNotPresentNestedBeneathOptionalFieldThatIsPresentIsConsideredMissing() { + void describedFieldThatIsNotPresentNestedBeneathOptionalFieldThatIsPresentIsConsideredMissing() { List descriptors = Arrays.asList(new FieldDescriptor("a").optional(), new FieldDescriptor("b"), new FieldDescriptor("a.c")); List missingFields = new JsonContentHandler("{\"a\":\"alpha\",\"b\":\"bravo\"}".getBytes(), @@ -150,7 +150,7 @@ public void describedFieldThatIsNotPresentNestedBeneathOptionalFieldThatIsPresen } @Test - public void describedFieldThatIsNotPresentNestedBeneathOptionalFieldThatIsNotPresentIsNotConsideredMissing() { + void describedFieldThatIsNotPresentNestedBeneathOptionalFieldThatIsNotPresentIsNotConsideredMissing() { List descriptors = Arrays.asList(new FieldDescriptor("a").optional(), new FieldDescriptor("b"), new FieldDescriptor("a.c")); List missingFields = new JsonContentHandler("{\"b\":\"bravo\"}".getBytes(), descriptors) @@ -159,7 +159,7 @@ public void describedFieldThatIsNotPresentNestedBeneathOptionalFieldThatIsNotPre } @Test - public void describedFieldThatIsNotPresentNestedBeneathOptionalArrayThatIsEmptyIsNotConsideredMissing() { + void describedFieldThatIsNotPresentNestedBeneathOptionalArrayThatIsEmptyIsNotConsideredMissing() { List descriptors = Arrays.asList(new FieldDescriptor("outer"), new FieldDescriptor("outer[]").optional(), new FieldDescriptor("outer[].inner")); List missingFields = new JsonContentHandler("{\"outer\":[]}".getBytes(), descriptors) @@ -168,7 +168,7 @@ public void describedFieldThatIsNotPresentNestedBeneathOptionalArrayThatIsEmptyI } @Test - public void describedSometimesPresentFieldThatIsChildOfSometimesPresentOptionalArrayIsNotConsideredMissing() { + void describedSometimesPresentFieldThatIsChildOfSometimesPresentOptionalArrayIsNotConsideredMissing() { List descriptors = Arrays.asList(new FieldDescriptor("a.[].c").optional(), new FieldDescriptor("a.[].c.d")); List missingFields = new JsonContentHandler( @@ -178,7 +178,7 @@ public void describedSometimesPresentFieldThatIsChildOfSometimesPresentOptionalA } @Test - public void describedMissingFieldThatIsChildOfNestedOptionalArrayThatIsEmptyIsNotConsideredMissing() { + void describedMissingFieldThatIsChildOfNestedOptionalArrayThatIsEmptyIsNotConsideredMissing() { List descriptors = Arrays.asList(new FieldDescriptor("a.[].b").optional(), new FieldDescriptor("a.[].b.[]").optional(), new FieldDescriptor("a.[].b.[].c")); List missingFields = new JsonContentHandler("{\"a\":[{\"b\":[]}]}".getBytes(), descriptors) @@ -187,7 +187,7 @@ public void describedMissingFieldThatIsChildOfNestedOptionalArrayThatIsEmptyIsNo } @Test - public void describedMissingFieldThatIsChildOfNestedOptionalArrayThatContainsAnObjectIsConsideredMissing() { + void describedMissingFieldThatIsChildOfNestedOptionalArrayThatContainsAnObjectIsConsideredMissing() { List descriptors = Arrays.asList(new FieldDescriptor("a.[].b").optional(), new FieldDescriptor("a.[].b.[]").optional(), new FieldDescriptor("a.[].b.[].c")); List missingFields = new JsonContentHandler("{\"a\":[{\"b\":[{}]}]}".getBytes(), descriptors) @@ -197,7 +197,7 @@ public void describedMissingFieldThatIsChildOfNestedOptionalArrayThatContainsAnO } @Test - public void describedMissingFieldThatIsChildOfOptionalObjectThatIsNullIsNotConsideredMissing() { + void describedMissingFieldThatIsChildOfOptionalObjectThatIsNullIsNotConsideredMissing() { List descriptors = Arrays.asList(new FieldDescriptor("a").optional(), new FieldDescriptor("a.b")); List missingFields = new JsonContentHandler("{\"a\":null}".getBytes(), descriptors) diff --git a/spring-restdocs-core/src/test/java/org/springframework/restdocs/payload/JsonFieldPathTests.java b/spring-restdocs-core/src/test/java/org/springframework/restdocs/payload/JsonFieldPathTests.java index d8c85efe..ec91dee3 100644 --- a/spring-restdocs-core/src/test/java/org/springframework/restdocs/payload/JsonFieldPathTests.java +++ b/spring-restdocs-core/src/test/java/org/springframework/restdocs/payload/JsonFieldPathTests.java @@ -16,7 +16,7 @@ package org.springframework.restdocs.payload; -import org.junit.Test; +import org.junit.jupiter.api.Test; import org.springframework.restdocs.payload.JsonFieldPath.PathType; @@ -28,131 +28,131 @@ * @author Andy Wilkinson * @author Jeremy Rickard */ -public class JsonFieldPathTests { +class JsonFieldPathTests { @Test - public void pathTypeOfSingleFieldIsSingle() { + void pathTypeOfSingleFieldIsSingle() { JsonFieldPath path = JsonFieldPath.compile("a"); assertThat(path.getType()).isEqualTo(PathType.SINGLE); } @Test - public void pathTypeOfSingleNestedFieldIsSingle() { + void pathTypeOfSingleNestedFieldIsSingle() { JsonFieldPath path = JsonFieldPath.compile("a.b"); assertThat(path.getType()).isEqualTo(PathType.SINGLE); } @Test - public void pathTypeOfTopLevelArrayIsSingle() { + void pathTypeOfTopLevelArrayIsSingle() { JsonFieldPath path = JsonFieldPath.compile("[]"); assertThat(path.getType()).isEqualTo(PathType.SINGLE); } @Test - public void pathTypeOfFieldBeneathTopLevelArrayIsMulti() { + void pathTypeOfFieldBeneathTopLevelArrayIsMulti() { JsonFieldPath path = JsonFieldPath.compile("[]a"); assertThat(path.getType()).isEqualTo(PathType.MULTI); } @Test - public void pathTypeOfSingleNestedArrayIsSingle() { + void pathTypeOfSingleNestedArrayIsSingle() { JsonFieldPath path = JsonFieldPath.compile("a[]"); assertThat(path.getType()).isEqualTo(PathType.SINGLE); } @Test - public void pathTypeOfArrayBeneathNestedFieldsIsSingle() { + void pathTypeOfArrayBeneathNestedFieldsIsSingle() { JsonFieldPath path = JsonFieldPath.compile("a.b[]"); assertThat(path.getType()).isEqualTo(PathType.SINGLE); } @Test - public void pathTypeOfArrayOfArraysIsMulti() { + void pathTypeOfArrayOfArraysIsMulti() { JsonFieldPath path = JsonFieldPath.compile("a[][]"); assertThat(path.getType()).isEqualTo(PathType.MULTI); } @Test - public void pathTypeOfFieldBeneathAnArrayIsMulti() { + void pathTypeOfFieldBeneathAnArrayIsMulti() { JsonFieldPath path = JsonFieldPath.compile("a[].b"); assertThat(path.getType()).isEqualTo(PathType.MULTI); } @Test - public void pathTypeOfFieldBeneathTopLevelWildcardIsMulti() { + void pathTypeOfFieldBeneathTopLevelWildcardIsMulti() { JsonFieldPath path = JsonFieldPath.compile("*.a"); assertThat(path.getType()).isEqualTo(PathType.MULTI); } @Test - public void pathTypeOfFieldBeneathNestedWildcardIsMulti() { + void pathTypeOfFieldBeneathNestedWildcardIsMulti() { JsonFieldPath path = JsonFieldPath.compile("a.*.b"); assertThat(path.getType()).isEqualTo(PathType.MULTI); } @Test - public void pathTypeOfLeafWidlcardIsMulti() { + void pathTypeOfLeafWidlcardIsMulti() { JsonFieldPath path = JsonFieldPath.compile("a.*"); assertThat(path.getType()).isEqualTo(PathType.MULTI); } @Test - public void compilationOfSingleElementPath() { + void compilationOfSingleElementPath() { assertThat(JsonFieldPath.compile("a").getSegments()).containsExactly("a"); } @Test - public void compilationOfMultipleElementPath() { + void compilationOfMultipleElementPath() { assertThat(JsonFieldPath.compile("a.b.c").getSegments()).containsExactly("a", "b", "c"); } @Test - public void compilationOfPathWithArraysWithNoDotSeparators() { + void compilationOfPathWithArraysWithNoDotSeparators() { assertThat(JsonFieldPath.compile("a[]b[]c").getSegments()).containsExactly("a", "[]", "b", "[]", "c"); } @Test - public void compilationOfPathWithArraysWithPreAndPostDotSeparators() { + void compilationOfPathWithArraysWithPreAndPostDotSeparators() { assertThat(JsonFieldPath.compile("a.[].b.[].c").getSegments()).containsExactly("a", "[]", "b", "[]", "c"); } @Test - public void compilationOfPathWithArraysWithPreDotSeparators() { + void compilationOfPathWithArraysWithPreDotSeparators() { assertThat(JsonFieldPath.compile("a.[]b.[]c").getSegments()).containsExactly("a", "[]", "b", "[]", "c"); } @Test - public void compilationOfPathWithArraysWithPostDotSeparators() { + void compilationOfPathWithArraysWithPostDotSeparators() { assertThat(JsonFieldPath.compile("a[].b[].c").getSegments()).containsExactly("a", "[]", "b", "[]", "c"); } @Test - public void compilationOfPathStartingWithAnArray() { + void compilationOfPathStartingWithAnArray() { assertThat(JsonFieldPath.compile("[]a.b.c").getSegments()).containsExactly("[]", "a", "b", "c"); } @Test - public void compilationOfMultipleElementPathWithBrackets() { + void compilationOfMultipleElementPathWithBrackets() { assertThat(JsonFieldPath.compile("['a']['b']['c']").getSegments()).containsExactly("a", "b", "c"); } @Test - public void compilationOfMultipleElementPathWithAndWithoutBrackets() { + void compilationOfMultipleElementPathWithAndWithoutBrackets() { assertThat(JsonFieldPath.compile("['a'][].b['c']").getSegments()).containsExactly("a", "[]", "b", "c"); } @Test - public void compilationOfMultipleElementPathWithAndWithoutBracketsAndEmbeddedDots() { + void compilationOfMultipleElementPathWithAndWithoutBracketsAndEmbeddedDots() { assertThat(JsonFieldPath.compile("['a.key'][].b['c']").getSegments()).containsExactly("a.key", "[]", "b", "c"); } @Test - public void compilationOfPathWithAWildcard() { + void compilationOfPathWithAWildcard() { assertThat(JsonFieldPath.compile("a.b.*.c").getSegments()).containsExactly("a", "b", "*", "c"); } @Test - public void compilationOfPathWithAWildcardInBrackets() { + void compilationOfPathWithAWildcardInBrackets() { assertThat(JsonFieldPath.compile("a.b.['*'].c").getSegments()).containsExactly("a", "b", "*", "c"); } diff --git a/spring-restdocs-core/src/test/java/org/springframework/restdocs/payload/JsonFieldPathsTests.java b/spring-restdocs-core/src/test/java/org/springframework/restdocs/payload/JsonFieldPathsTests.java index 3ddce450..c0298166 100644 --- a/spring-restdocs-core/src/test/java/org/springframework/restdocs/payload/JsonFieldPathsTests.java +++ b/spring-restdocs-core/src/test/java/org/springframework/restdocs/payload/JsonFieldPathsTests.java @@ -16,11 +16,10 @@ package org.springframework.restdocs.payload; -import java.io.IOException; import java.util.Arrays; -import com.fasterxml.jackson.databind.ObjectMapper; -import org.junit.Test; +import org.junit.jupiter.api.Test; +import tools.jackson.databind.ObjectMapper; import org.springframework.restdocs.payload.JsonFieldProcessor.ExtractedField; @@ -31,10 +30,10 @@ * * @author Andy Wilkinson */ -public class JsonFieldPathsTests { +class JsonFieldPathsTests { @Test - public void noUncommonPathsForSingleItem() { + void noUncommonPathsForSingleItem() { assertThat( JsonFieldPaths.from(Arrays.asList(json("{\"a\": 1, \"b\": [ { \"c\": 2}, {\"c\": 3}, {\"c\": null}]}"))) .getUncommon()) @@ -42,20 +41,20 @@ public void noUncommonPathsForSingleItem() { } @Test - public void noUncommonPathsForMultipleIdenticalItems() { + void noUncommonPathsForMultipleIdenticalItems() { Object item = json("{\"a\": 1, \"b\": [ { \"c\": 2}, {\"c\": 3} ]}"); assertThat(JsonFieldPaths.from(Arrays.asList(item, item)).getUncommon()).isEmpty(); } @Test - public void noUncommonPathsForMultipleMatchingItemsWithDifferentScalarValues() { + void noUncommonPathsForMultipleMatchingItemsWithDifferentScalarValues() { assertThat(JsonFieldPaths.from(Arrays.asList(json("{\"a\": 1, \"b\": [ { \"c\": 2}, {\"c\": 3} ]}"), json("{\"a\": 4, \"b\": [ { \"c\": 5}, {\"c\": 6} ]}"))) .getUncommon()).isEmpty(); } @Test - public void missingEntryInMapIsIdentifiedAsUncommon() { + void missingEntryInMapIsIdentifiedAsUncommon() { assertThat( JsonFieldPaths.from(Arrays.asList(json("{\"a\": 1}"), json("{\"a\": 1}"), json("{\"a\": 1, \"b\": 2}"))) .getUncommon()) @@ -63,21 +62,21 @@ public void missingEntryInMapIsIdentifiedAsUncommon() { } @Test - public void missingEntryInNestedMapIsIdentifiedAsUncommon() { + void missingEntryInNestedMapIsIdentifiedAsUncommon() { assertThat(JsonFieldPaths.from(Arrays.asList(json("{\"a\": 1, \"b\": {\"c\": 1}}"), json("{\"a\": 1, \"b\": {\"c\": 1}}"), json("{\"a\": 1, \"b\": {\"c\": 1, \"d\": 2}}"))) .getUncommon()).containsExactly("b.d"); } @Test - public void missingEntriesInNestedMapAreIdentifiedAsUncommon() { + void missingEntriesInNestedMapAreIdentifiedAsUncommon() { assertThat(JsonFieldPaths.from(Arrays.asList(json("{\"a\": 1, \"b\": {\"c\": 1}}"), json("{\"a\": 1, \"b\": {\"c\": 1}}"), json("{\"a\": 1, \"b\": {\"d\": 2}}"))) .getUncommon()).containsExactly("b.c", "b.d"); } @Test - public void absentItemFromFieldExtractionCausesAllPresentFieldsToBeIdentifiedAsUncommon() { + void absentItemFromFieldExtractionCausesAllPresentFieldsToBeIdentifiedAsUncommon() { assertThat(JsonFieldPaths .from(Arrays.asList(ExtractedField.ABSENT, json("{\"a\": 1, \"b\": {\"c\": 1}}"), json("{\"a\": 1, \"b\": {\"c\": 1}}"), json("{\"a\": 1, \"b\": {\"d\": 2}}"))) @@ -85,26 +84,21 @@ public void absentItemFromFieldExtractionCausesAllPresentFieldsToBeIdentifiedAsU } @Test - public void missingEntryBeneathArrayIsIdentifiedAsUncommon() { + void missingEntryBeneathArrayIsIdentifiedAsUncommon() { assertThat(JsonFieldPaths .from(Arrays.asList(json("[{\"b\": 1}]"), json("[{\"b\": 1}]"), json("[{\"b\": 1, \"c\": 2}]"))) .getUncommon()).containsExactly("[].c"); } @Test - public void missingEntryBeneathNestedArrayIsIdentifiedAsUncommon() { + void missingEntryBeneathNestedArrayIsIdentifiedAsUncommon() { assertThat(JsonFieldPaths.from(Arrays.asList(json("{\"a\": [{\"b\": 1}]}"), json("{\"a\": [{\"b\": 1}]}"), json("{\"a\": [{\"b\": 1, \"c\": 2}]}"))) .getUncommon()).containsExactly("a.[].c"); } private Object json(String json) { - try { - return new ObjectMapper().readValue(json, Object.class); - } - catch (IOException ex) { - throw new RuntimeException(ex); - } + return new ObjectMapper().readValue(json, Object.class); } } diff --git a/spring-restdocs-core/src/test/java/org/springframework/restdocs/payload/JsonFieldProcessorTests.java b/spring-restdocs-core/src/test/java/org/springframework/restdocs/payload/JsonFieldProcessorTests.java index 1c6f9fa6..66f42bfa 100644 --- a/spring-restdocs-core/src/test/java/org/springframework/restdocs/payload/JsonFieldProcessorTests.java +++ b/spring-restdocs-core/src/test/java/org/springframework/restdocs/payload/JsonFieldProcessorTests.java @@ -16,7 +16,6 @@ package org.springframework.restdocs.payload; -import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -25,8 +24,8 @@ import java.util.List; import java.util.Map; -import com.fasterxml.jackson.databind.ObjectMapper; -import org.junit.Test; +import org.junit.jupiter.api.Test; +import tools.jackson.databind.ObjectMapper; import org.springframework.restdocs.payload.JsonFieldProcessor.ExtractedField; @@ -37,19 +36,19 @@ * * @author Andy Wilkinson */ -public class JsonFieldProcessorTests { +class JsonFieldProcessorTests { private final JsonFieldProcessor fieldProcessor = new JsonFieldProcessor(); @Test - public void extractTopLevelMapEntry() { + void extractTopLevelMapEntry() { Map payload = new HashMap<>(); payload.put("a", "alpha"); assertThat(this.fieldProcessor.extract("a", payload).getValue()).isEqualTo("alpha"); } @Test - public void extractNestedMapEntry() { + void extractNestedMapEntry() { Map payload = new HashMap<>(); Map alpha = new HashMap<>(); payload.put("a", alpha); @@ -58,7 +57,7 @@ public void extractNestedMapEntry() { } @Test - public void extractTopLevelArray() { + void extractTopLevelArray() { List> payload = new ArrayList<>(); Map bravo = new HashMap<>(); bravo.put("b", "bravo"); @@ -68,7 +67,7 @@ public void extractTopLevelArray() { } @Test - public void extractArray() { + void extractArray() { Map payload = new HashMap<>(); Map bravo = new HashMap<>(); bravo.put("b", "bravo"); @@ -78,7 +77,7 @@ public void extractArray() { } @Test - public void extractArrayContents() { + void extractArrayContents() { Map payload = new HashMap<>(); Map bravo = new HashMap<>(); bravo.put("b", "bravo"); @@ -88,7 +87,7 @@ public void extractArrayContents() { } @Test - public void extractFromItemsInArray() { + void extractFromItemsInArray() { Map payload = new HashMap<>(); Map entry = new HashMap<>(); entry.put("b", "bravo"); @@ -98,7 +97,7 @@ public void extractFromItemsInArray() { } @Test - public void extractOccasionallyAbsentFieldFromItemsInArray() { + void extractOccasionallyAbsentFieldFromItemsInArray() { Map payload = new HashMap<>(); Map entry = new HashMap<>(); entry.put("b", "bravo"); @@ -109,7 +108,7 @@ public void extractOccasionallyAbsentFieldFromItemsInArray() { } @Test - public void extractOccasionallyNullFieldFromItemsInArray() { + void extractOccasionallyNullFieldFromItemsInArray() { Map payload = new HashMap<>(); Map nonNullField = new HashMap<>(); nonNullField.put("b", "bravo"); @@ -121,7 +120,7 @@ public void extractOccasionallyNullFieldFromItemsInArray() { } @Test - public void extractNestedArray() { + void extractNestedArray() { Map payload = new HashMap<>(); Map entry1 = createEntry("id:1"); Map entry2 = createEntry("id:2"); @@ -133,7 +132,7 @@ public void extractNestedArray() { } @Test - public void extractFromItemsInNestedArray() { + void extractFromItemsInNestedArray() { Map payload = new HashMap<>(); Map entry1 = createEntry("id:1"); Map entry2 = createEntry("id:2"); @@ -144,7 +143,7 @@ public void extractFromItemsInNestedArray() { } @Test - public void extractArraysFromItemsInNestedArray() { + void extractArraysFromItemsInNestedArray() { Map payload = new HashMap<>(); Map entry1 = createEntry("ids", Arrays.asList(1, 2)); Map entry2 = createEntry("ids", Arrays.asList(3)); @@ -156,27 +155,27 @@ public void extractArraysFromItemsInNestedArray() { } @Test - public void nonExistentTopLevelField() { + void nonExistentTopLevelField() { assertThat(this.fieldProcessor.extract("a", Collections.emptyMap()).getValue()) .isEqualTo(ExtractedField.ABSENT); } @Test - public void nonExistentNestedField() { + void nonExistentNestedField() { HashMap payload = new HashMap<>(); - payload.put("a", new HashMap()); + payload.put("a", new HashMap<>()); assertThat(this.fieldProcessor.extract("a.b", payload).getValue()).isEqualTo(ExtractedField.ABSENT); } @Test - public void nonExistentNestedFieldWhenParentIsNotAMap() { + void nonExistentNestedFieldWhenParentIsNotAMap() { HashMap payload = new HashMap<>(); payload.put("a", 5); assertThat(this.fieldProcessor.extract("a.b", payload).getValue()).isEqualTo(ExtractedField.ABSENT); } @Test - public void nonExistentFieldWhenParentIsAnArray() { + void nonExistentFieldWhenParentIsAnArray() { HashMap payload = new HashMap<>(); HashMap alpha = new HashMap<>(); alpha.put("b", Arrays.asList(new HashMap())); @@ -185,20 +184,20 @@ public void nonExistentFieldWhenParentIsAnArray() { } @Test - public void nonExistentArrayField() { + void nonExistentArrayField() { HashMap payload = new HashMap<>(); assertThat(this.fieldProcessor.extract("a[]", payload).getValue()).isEqualTo(ExtractedField.ABSENT); } @Test - public void nonExistentArrayFieldAsTypeDoesNotMatch() { + void nonExistentArrayFieldAsTypeDoesNotMatch() { HashMap payload = new HashMap<>(); payload.put("a", 5); assertThat(this.fieldProcessor.extract("a[]", payload).getValue()).isEqualTo(ExtractedField.ABSENT); } @Test - public void nonExistentFieldBeneathAnArray() { + void nonExistentFieldBeneathAnArray() { HashMap payload = new HashMap<>(); HashMap alpha = new HashMap<>(); alpha.put("b", Arrays.asList(new HashMap())); @@ -208,7 +207,7 @@ public void nonExistentFieldBeneathAnArray() { } @Test - public void removeTopLevelMapEntry() { + void removeTopLevelMapEntry() { Map payload = new HashMap<>(); payload.put("a", "alpha"); this.fieldProcessor.remove("a", payload); @@ -216,7 +215,7 @@ public void removeTopLevelMapEntry() { } @Test - public void mapWithEntriesIsNotRemovedWhenNotAlsoRemovingDescendants() { + void mapWithEntriesIsNotRemovedWhenNotAlsoRemovingDescendants() { Map payload = new HashMap<>(); Map alpha = new HashMap<>(); payload.put("a", alpha); @@ -226,7 +225,7 @@ public void mapWithEntriesIsNotRemovedWhenNotAlsoRemovingDescendants() { } @Test - public void removeSubsectionRemovesMapWithEntries() { + void removeSubsectionRemovesMapWithEntries() { Map payload = new HashMap<>(); Map alpha = new HashMap<>(); payload.put("a", alpha); @@ -236,7 +235,7 @@ public void removeSubsectionRemovesMapWithEntries() { } @Test - public void removeNestedMapEntry() { + void removeNestedMapEntry() { Map payload = new HashMap<>(); Map alpha = new HashMap<>(); payload.put("a", alpha); @@ -247,7 +246,7 @@ public void removeNestedMapEntry() { @SuppressWarnings("unchecked") @Test - public void removeItemsInArray() throws IOException { + void removeItemsInArray() { Map payload = new ObjectMapper().readValue("{\"a\": [{\"b\":\"bravo\"},{\"b\":\"bravo\"}]}", Map.class); this.fieldProcessor.remove("a[].b", payload); @@ -256,7 +255,7 @@ public void removeItemsInArray() throws IOException { @SuppressWarnings("unchecked") @Test - public void removeItemsInNestedArray() throws IOException { + void removeItemsInNestedArray() { Map payload = new ObjectMapper().readValue("{\"a\": [[{\"id\":1},{\"id\":2}], [{\"id\":3}]]}", Map.class); this.fieldProcessor.remove("a[][].id", payload); @@ -265,7 +264,7 @@ public void removeItemsInNestedArray() throws IOException { @SuppressWarnings("unchecked") @Test - public void removeDoesNotRemoveArrayWithMapEntries() throws IOException { + void removeDoesNotRemoveArrayWithMapEntries() { Map payload = new ObjectMapper().readValue("{\"a\": [{\"b\":\"bravo\"},{\"b\":\"bravo\"}]}", Map.class); this.fieldProcessor.remove("a[]", payload); @@ -274,7 +273,7 @@ public void removeDoesNotRemoveArrayWithMapEntries() throws IOException { @SuppressWarnings("unchecked") @Test - public void removeDoesNotRemoveArrayWithListEntries() throws IOException { + void removeDoesNotRemoveArrayWithListEntries() { Map payload = new ObjectMapper().readValue("{\"a\": [[2],[3]]}", Map.class); this.fieldProcessor.remove("a[]", payload); assertThat(payload.size()).isEqualTo(1); @@ -282,7 +281,7 @@ public void removeDoesNotRemoveArrayWithListEntries() throws IOException { @SuppressWarnings("unchecked") @Test - public void removeRemovesArrayWithOnlyScalarEntries() throws IOException { + void removeRemovesArrayWithOnlyScalarEntries() { Map payload = new ObjectMapper().readValue("{\"a\": [\"bravo\", \"charlie\"]}", Map.class); this.fieldProcessor.remove("a", payload); assertThat(payload.size()).isEqualTo(0); @@ -290,7 +289,7 @@ public void removeRemovesArrayWithOnlyScalarEntries() throws IOException { @SuppressWarnings("unchecked") @Test - public void removeSubsectionRemovesArrayWithMapEntries() throws IOException { + void removeSubsectionRemovesArrayWithMapEntries() { Map payload = new ObjectMapper().readValue("{\"a\": [{\"b\":\"bravo\"},{\"b\":\"bravo\"}]}", Map.class); this.fieldProcessor.removeSubsection("a[]", payload); @@ -299,14 +298,14 @@ public void removeSubsectionRemovesArrayWithMapEntries() throws IOException { @SuppressWarnings("unchecked") @Test - public void removeSubsectionRemovesArrayWithListEntries() throws IOException { + void removeSubsectionRemovesArrayWithListEntries() { Map payload = new ObjectMapper().readValue("{\"a\": [[2],[3]]}", Map.class); this.fieldProcessor.removeSubsection("a[]", payload); assertThat(payload.size()).isEqualTo(0); } @Test - public void extractNestedEntryWithDotInKeys() { + void extractNestedEntryWithDotInKeys() { Map payload = new HashMap<>(); Map alpha = new HashMap<>(); payload.put("a.key", alpha); @@ -316,7 +315,7 @@ public void extractNestedEntryWithDotInKeys() { @SuppressWarnings("unchecked") @Test - public void extractNestedEntriesUsingTopLevelWildcard() { + void extractNestedEntriesUsingTopLevelWildcard() { Map payload = new LinkedHashMap<>(); Map alpha = new LinkedHashMap<>(); payload.put("a", alpha); @@ -330,7 +329,7 @@ public void extractNestedEntriesUsingTopLevelWildcard() { @SuppressWarnings("unchecked") @Test - public void extractNestedEntriesUsingMidLevelWildcard() { + void extractNestedEntriesUsingMidLevelWildcard() { Map payload = new LinkedHashMap<>(); Map alpha = new LinkedHashMap<>(); payload.put("a", alpha); @@ -344,7 +343,7 @@ public void extractNestedEntriesUsingMidLevelWildcard() { @SuppressWarnings("unchecked") @Test - public void extractUsingLeafWildcardMatchingSingleItem() { + void extractUsingLeafWildcardMatchingSingleItem() { Map payload = new HashMap<>(); Map alpha = new HashMap<>(); payload.put("a", alpha); @@ -357,7 +356,7 @@ public void extractUsingLeafWildcardMatchingSingleItem() { @SuppressWarnings("unchecked") @Test - public void extractUsingLeafWildcardMatchingMultipleItems() { + void extractUsingLeafWildcardMatchingMultipleItems() { Map payload = new HashMap<>(); Map alpha = new HashMap<>(); payload.put("a", alpha); @@ -368,7 +367,7 @@ public void extractUsingLeafWildcardMatchingMultipleItems() { } @Test - public void removeUsingLeafWildcard() { + void removeUsingLeafWildcard() { Map payload = new HashMap<>(); Map alpha = new HashMap<>(); payload.put("a", alpha); @@ -379,7 +378,7 @@ public void removeUsingLeafWildcard() { } @Test - public void removeUsingTopLevelWildcard() { + void removeUsingTopLevelWildcard() { Map payload = new HashMap<>(); Map alpha = new HashMap<>(); payload.put("a", alpha); @@ -390,7 +389,7 @@ public void removeUsingTopLevelWildcard() { } @Test - public void removeUsingMidLevelWildcard() { + void removeUsingMidLevelWildcard() { Map payload = new LinkedHashMap<>(); Map alpha = new LinkedHashMap<>(); payload.put("a", alpha); @@ -407,28 +406,28 @@ public void removeUsingMidLevelWildcard() { } @Test - public void hasFieldIsTrueForNonNullFieldInMap() { + void hasFieldIsTrueForNonNullFieldInMap() { Map payload = new HashMap<>(); payload.put("a", "alpha"); assertThat(this.fieldProcessor.hasField("a", payload)).isTrue(); } @Test - public void hasFieldIsTrueForNullFieldInMap() { + void hasFieldIsTrueForNullFieldInMap() { Map payload = new HashMap<>(); payload.put("a", null); assertThat(this.fieldProcessor.hasField("a", payload)).isTrue(); } @Test - public void hasFieldIsFalseForAbsentFieldInMap() { + void hasFieldIsFalseForAbsentFieldInMap() { Map payload = new HashMap<>(); payload.put("a", null); assertThat(this.fieldProcessor.hasField("b", payload)).isFalse(); } @Test - public void hasFieldIsTrueForNeverNullFieldBeneathArray() { + void hasFieldIsTrueForNeverNullFieldBeneathArray() { Map payload = new HashMap<>(); Map nested = new HashMap<>(); nested.put("b", "bravo"); @@ -437,7 +436,7 @@ public void hasFieldIsTrueForNeverNullFieldBeneathArray() { } @Test - public void hasFieldIsTrueForAlwaysNullFieldBeneathArray() { + void hasFieldIsTrueForAlwaysNullFieldBeneathArray() { Map payload = new HashMap<>(); Map nested = new HashMap<>(); nested.put("b", null); @@ -446,7 +445,7 @@ public void hasFieldIsTrueForAlwaysNullFieldBeneathArray() { } @Test - public void hasFieldIsFalseForAlwaysAbsentFieldBeneathArray() { + void hasFieldIsFalseForAlwaysAbsentFieldBeneathArray() { Map payload = new HashMap<>(); Map nested = new HashMap<>(); nested.put("b", "bravo"); @@ -455,7 +454,7 @@ public void hasFieldIsFalseForAlwaysAbsentFieldBeneathArray() { } @Test - public void hasFieldIsFalseForOccasionallyAbsentFieldBeneathArray() { + void hasFieldIsFalseForOccasionallyAbsentFieldBeneathArray() { Map payload = new HashMap<>(); Map nested = new HashMap<>(); nested.put("b", "bravo"); @@ -464,7 +463,7 @@ public void hasFieldIsFalseForOccasionallyAbsentFieldBeneathArray() { } @Test - public void hasFieldIsFalseForOccasionallyNullFieldBeneathArray() { + void hasFieldIsFalseForOccasionallyNullFieldBeneathArray() { Map payload = new HashMap<>(); Map fieldPresent = new HashMap<>(); fieldPresent.put("b", "bravo"); diff --git a/spring-restdocs-core/src/test/java/org/springframework/restdocs/payload/JsonFieldTypesDiscovererTests.java b/spring-restdocs-core/src/test/java/org/springframework/restdocs/payload/JsonFieldTypesDiscovererTests.java index 649de52b..ac10ff3c 100644 --- a/spring-restdocs-core/src/test/java/org/springframework/restdocs/payload/JsonFieldTypesDiscovererTests.java +++ b/spring-restdocs-core/src/test/java/org/springframework/restdocs/payload/JsonFieldTypesDiscovererTests.java @@ -16,10 +16,8 @@ package org.springframework.restdocs.payload; -import java.io.IOException; - -import com.fasterxml.jackson.databind.ObjectMapper; -import org.junit.Test; +import org.junit.jupiter.api.Test; +import tools.jackson.databind.ObjectMapper; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; @@ -29,157 +27,157 @@ * * @author Andy Wilkinson */ -public class JsonFieldTypesDiscovererTests { +class JsonFieldTypesDiscovererTests { private final JsonFieldTypesDiscoverer fieldTypeDiscoverer = new JsonFieldTypesDiscoverer(); @Test - public void arrayField() throws IOException { + void arrayField() { assertThat(discoverFieldTypes("[]")).containsExactly(JsonFieldType.ARRAY); } @Test - public void topLevelArray() throws IOException { + void topLevelArray() { assertThat(discoverFieldTypes("[]", "[{\"a\":\"alpha\"}]")).containsExactly(JsonFieldType.ARRAY); } @Test - public void nestedArray() throws IOException { + void nestedArray() { assertThat(discoverFieldTypes("a[]", "{\"a\": [{\"b\":\"bravo\"}]}")).containsExactly(JsonFieldType.ARRAY); } @Test - public void arrayNestedBeneathAnArray() throws IOException { + void arrayNestedBeneathAnArray() { assertThat(discoverFieldTypes("a[].b[]", "{\"a\": [{\"b\": [ 1, 2 ]}]}")).containsExactly(JsonFieldType.ARRAY); } @Test - public void specificFieldOfObjectInArrayNestedBeneathAnArray() throws IOException { + void specificFieldOfObjectInArrayNestedBeneathAnArray() { assertThat(discoverFieldTypes("a[].b[].c", "{\"a\": [{\"b\": [ {\"c\": 5}, {\"c\": 5}]}]}")) .containsExactly(JsonFieldType.NUMBER); } @Test - public void booleanField() throws IOException { + void booleanField() { assertThat(discoverFieldTypes("true")).containsExactly(JsonFieldType.BOOLEAN); } @Test - public void objectField() throws IOException { + void objectField() { assertThat(discoverFieldTypes("{}")).containsExactly(JsonFieldType.OBJECT); } @Test - public void nullField() throws IOException { + void nullField() { assertThat(discoverFieldTypes("null")).containsExactly(JsonFieldType.NULL); } @Test - public void numberField() throws IOException { + void numberField() { assertThat(discoverFieldTypes("1.2345")).containsExactly(JsonFieldType.NUMBER); } @Test - public void stringField() throws IOException { + void stringField() { assertThat(discoverFieldTypes("\"Foo\"")).containsExactly(JsonFieldType.STRING); } @Test - public void nestedField() throws IOException { + void nestedField() { assertThat(discoverFieldTypes("a.b.c", "{\"a\":{\"b\":{\"c\":{}}}}")).containsExactly(JsonFieldType.OBJECT); } @Test - public void multipleFieldsWithSameType() throws IOException { + void multipleFieldsWithSameType() { assertThat(discoverFieldTypes("a[].id", "{\"a\":[{\"id\":1},{\"id\":2}]}")) .containsExactly(JsonFieldType.NUMBER); } @Test - public void multipleFieldsWithDifferentTypes() throws IOException { + void multipleFieldsWithDifferentTypes() { assertThat(discoverFieldTypes("a[].id", "{\"a\":[{\"id\":1},{\"id\":true}]}")) .containsExactlyInAnyOrder(JsonFieldType.NUMBER, JsonFieldType.BOOLEAN); } @Test - public void multipleFieldsWithDifferentTypesAndSometimesAbsent() throws IOException { + void multipleFieldsWithDifferentTypesAndSometimesAbsent() { assertThat(discoverFieldTypes("a[].id", "{\"a\":[{\"id\":1},{\"id\":true}, {}]}")) .containsExactlyInAnyOrder(JsonFieldType.NUMBER, JsonFieldType.BOOLEAN, JsonFieldType.NULL); } @Test - public void multipleFieldsWhenSometimesAbsent() throws IOException { + void multipleFieldsWhenSometimesAbsent() { assertThat(discoverFieldTypes("a[].id", "{\"a\":[{\"id\":1},{\"id\":2}, {}]}")) .containsExactlyInAnyOrder(JsonFieldType.NUMBER, JsonFieldType.NULL); } @Test - public void multipleFieldsWhenSometimesNull() throws IOException { + void multipleFieldsWhenSometimesNull() { assertThat(discoverFieldTypes("a[].id", "{\"a\":[{\"id\":1},{\"id\":2}, {\"id\":null}]}")) .containsExactlyInAnyOrder(JsonFieldType.NUMBER, JsonFieldType.NULL); } @Test - public void multipleFieldsWithDifferentTypesAndSometimesNull() throws IOException { + void multipleFieldsWithDifferentTypesAndSometimesNull() { assertThat(discoverFieldTypes("a[].id", "{\"a\":[{\"id\":1},{\"id\":true}, {\"id\":null}]}")) .containsExactlyInAnyOrder(JsonFieldType.NUMBER, JsonFieldType.BOOLEAN, JsonFieldType.NULL); } @Test - public void multipleFieldsWhenEitherNullOrAbsent() throws IOException { + void multipleFieldsWhenEitherNullOrAbsent() { assertThat(discoverFieldTypes("a[].id", "{\"a\":[{},{\"id\":null}]}")) .containsExactlyInAnyOrder(JsonFieldType.NULL); } @Test - public void multipleFieldsThatAreAllNull() throws IOException { + void multipleFieldsThatAreAllNull() { assertThat(discoverFieldTypes("a[].id", "{\"a\":[{\"id\":null},{\"id\":null}]}")) .containsExactlyInAnyOrder(JsonFieldType.NULL); } @Test - public void nonExistentSingleFieldProducesFieldDoesNotExistException() { + void nonExistentSingleFieldProducesFieldDoesNotExistException() { assertThatExceptionOfType(FieldDoesNotExistException.class) .isThrownBy(() -> discoverFieldTypes("a.b", "{\"a\":{}}")) .withMessage("The payload does not contain a field with the path 'a.b'"); } @Test - public void nonExistentMultipleFieldsProducesFieldDoesNotExistException() { + void nonExistentMultipleFieldsProducesFieldDoesNotExistException() { assertThatExceptionOfType(FieldDoesNotExistException.class) .isThrownBy(() -> discoverFieldTypes("a[].b", "{\"a\":[{\"c\":1},{\"c\":2}]}")) .withMessage("The payload does not contain a field with the path 'a[].b'"); } @Test - public void leafWildcardWithCommonType() throws IOException { + void leafWildcardWithCommonType() { assertThat(discoverFieldTypes("a.*", "{\"a\": {\"b\": 5, \"c\": 6}}")) .containsExactlyInAnyOrder(JsonFieldType.NUMBER); } @Test - public void leafWildcardWithVaryingType() throws IOException { + void leafWildcardWithVaryingType() { assertThat(discoverFieldTypes("a.*", "{\"a\": {\"b\": 5, \"c\": \"six\"}}")) .containsExactlyInAnyOrder(JsonFieldType.NUMBER, JsonFieldType.STRING); } @Test - public void intermediateWildcardWithCommonType() throws IOException { - assertThat(discoverFieldTypes("a.*.d", "{\"a\": {\"b\": {\"d\": 4}, \"c\": {\"d\": 5}}}}")) + void intermediateWildcardWithCommonType() { + assertThat(discoverFieldTypes("a.*.d", "{\"a\": {\"b\": {\"d\": 4}, \"c\": {\"d\": 5}}}")) .containsExactlyInAnyOrder(JsonFieldType.NUMBER); } @Test - public void intermediateWildcardWithVaryingType() throws IOException { - assertThat(discoverFieldTypes("a.*.d", "{\"a\": {\"b\": {\"d\": 4}, \"c\": {\"d\": \"four\"}}}}")) + void intermediateWildcardWithVaryingType() { + assertThat(discoverFieldTypes("a.*.d", "{\"a\": {\"b\": {\"d\": 4}, \"c\": {\"d\": \"four\"}}}")) .containsExactlyInAnyOrder(JsonFieldType.NUMBER, JsonFieldType.STRING); } - private JsonFieldTypes discoverFieldTypes(String value) throws IOException { + private JsonFieldTypes discoverFieldTypes(String value) { return discoverFieldTypes("field", "{\"field\":" + value + "}"); } - private JsonFieldTypes discoverFieldTypes(String path, String json) throws IOException { + private JsonFieldTypes discoverFieldTypes(String path, String json) { return this.fieldTypeDiscoverer.discoverFieldTypes(path, new ObjectMapper().readValue(json, Object.class)); } diff --git a/spring-restdocs-core/src/test/java/org/springframework/restdocs/payload/JsonFieldTypesTests.java b/spring-restdocs-core/src/test/java/org/springframework/restdocs/payload/JsonFieldTypesTests.java index b28b6cf8..70f65d3a 100644 --- a/spring-restdocs-core/src/test/java/org/springframework/restdocs/payload/JsonFieldTypesTests.java +++ b/spring-restdocs-core/src/test/java/org/springframework/restdocs/payload/JsonFieldTypesTests.java @@ -18,7 +18,7 @@ import java.util.EnumSet; -import org.junit.Test; +import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThat; @@ -27,32 +27,32 @@ * * @author Andy Wilkinson */ -public class JsonFieldTypesTests { +class JsonFieldTypesTests { @Test - public void singleTypeCoalescesToThatType() { + void singleTypeCoalescesToThatType() { assertThat(new JsonFieldTypes(JsonFieldType.NUMBER).coalesce(false)).isEqualTo(JsonFieldType.NUMBER); } @Test - public void singleTypeCoalescesToThatTypeWhenOptional() { + void singleTypeCoalescesToThatTypeWhenOptional() { assertThat(new JsonFieldTypes(JsonFieldType.NUMBER).coalesce(true)).isEqualTo(JsonFieldType.NUMBER); } @Test - public void multipleTypesCoalescesToVaries() { + void multipleTypesCoalescesToVaries() { assertThat(new JsonFieldTypes(EnumSet.of(JsonFieldType.ARRAY, JsonFieldType.NUMBER)).coalesce(false)) .isEqualTo(JsonFieldType.VARIES); } @Test - public void nullAndNonNullTypesCoalescesToVaries() { + void nullAndNonNullTypesCoalescesToVaries() { assertThat(new JsonFieldTypes(EnumSet.of(JsonFieldType.ARRAY, JsonFieldType.NULL)).coalesce(false)) .isEqualTo(JsonFieldType.VARIES); } @Test - public void nullAndNonNullTypesCoalescesToNonNullTypeWhenOptional() { + void nullAndNonNullTypesCoalescesToNonNullTypeWhenOptional() { assertThat(new JsonFieldTypes(EnumSet.of(JsonFieldType.ARRAY, JsonFieldType.NULL)).coalesce(true)) .isEqualTo(JsonFieldType.ARRAY); } diff --git a/spring-restdocs-core/src/test/java/org/springframework/restdocs/payload/PayloadDocumentationTests.java b/spring-restdocs-core/src/test/java/org/springframework/restdocs/payload/PayloadDocumentationTests.java index 7581d5ed..b79ab387 100644 --- a/spring-restdocs-core/src/test/java/org/springframework/restdocs/payload/PayloadDocumentationTests.java +++ b/spring-restdocs-core/src/test/java/org/springframework/restdocs/payload/PayloadDocumentationTests.java @@ -19,7 +19,7 @@ import java.util.Arrays; import java.util.List; -import org.junit.Test; +import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThat; import static org.springframework.restdocs.payload.PayloadDocumentation.applyPathPrefix; @@ -31,10 +31,10 @@ * * @author Andy Wilkinson */ -public class PayloadDocumentationTests { +class PayloadDocumentationTests { @Test - public void applyPathPrefixAppliesPrefixToDescriptorPaths() { + void applyPathPrefixAppliesPrefixToDescriptorPaths() { List descriptors = applyPathPrefix("alpha.", Arrays.asList(fieldWithPath("bravo"), fieldWithPath("charlie"))); assertThat(descriptors.size()).isEqualTo(2); @@ -42,21 +42,21 @@ public void applyPathPrefixAppliesPrefixToDescriptorPaths() { } @Test - public void applyPathPrefixCopiesIgnored() { + void applyPathPrefixCopiesIgnored() { List descriptors = applyPathPrefix("alpha.", Arrays.asList(fieldWithPath("bravo").ignored())); assertThat(descriptors.size()).isEqualTo(1); assertThat(descriptors.get(0).isIgnored()).isTrue(); } @Test - public void applyPathPrefixCopiesOptional() { + void applyPathPrefixCopiesOptional() { List descriptors = applyPathPrefix("alpha.", Arrays.asList(fieldWithPath("bravo").optional())); assertThat(descriptors.size()).isEqualTo(1); assertThat(descriptors.get(0).isOptional()).isTrue(); } @Test - public void applyPathPrefixCopiesDescription() { + void applyPathPrefixCopiesDescription() { List descriptors = applyPathPrefix("alpha.", Arrays.asList(fieldWithPath("bravo").description("Some field"))); assertThat(descriptors.size()).isEqualTo(1); @@ -64,7 +64,7 @@ public void applyPathPrefixCopiesDescription() { } @Test - public void applyPathPrefixCopiesType() { + void applyPathPrefixCopiesType() { List descriptors = applyPathPrefix("alpha.", Arrays.asList(fieldWithPath("bravo").type(JsonFieldType.OBJECT))); assertThat(descriptors.size()).isEqualTo(1); @@ -72,7 +72,7 @@ public void applyPathPrefixCopiesType() { } @Test - public void applyPathPrefixCopiesAttributes() { + void applyPathPrefixCopiesAttributes() { List descriptors = applyPathPrefix("alpha.", Arrays.asList(fieldWithPath("bravo").attributes(key("a").value("alpha"), key("b").value("bravo")))); assertThat(descriptors.size()).isEqualTo(1); diff --git a/spring-restdocs-core/src/test/java/org/springframework/restdocs/payload/RequestBodyPartSnippetTests.java b/spring-restdocs-core/src/test/java/org/springframework/restdocs/payload/RequestBodyPartSnippetTests.java index de9ad3d0..9b75605c 100644 --- a/spring-restdocs-core/src/test/java/org/springframework/restdocs/payload/RequestBodyPartSnippetTests.java +++ b/spring-restdocs-core/src/test/java/org/springframework/restdocs/payload/RequestBodyPartSnippetTests.java @@ -18,19 +18,14 @@ import java.io.IOException; -import org.junit.Test; - import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; -import org.springframework.restdocs.AbstractSnippetTests; -import org.springframework.restdocs.templates.TemplateEngine; -import org.springframework.restdocs.templates.TemplateFormat; -import org.springframework.restdocs.templates.TemplateResourceResolver; -import org.springframework.restdocs.templates.mustache.MustacheTemplateEngine; +import org.springframework.restdocs.testfixtures.jupiter.AssertableSnippets; +import org.springframework.restdocs.testfixtures.jupiter.OperationBuilder; +import org.springframework.restdocs.testfixtures.jupiter.RenderedSnippetTest; +import org.springframework.restdocs.testfixtures.jupiter.SnippetTemplate; import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.BDDMockito.given; -import static org.mockito.Mockito.mock; import static org.springframework.restdocs.payload.PayloadDocumentation.beneathPath; import static org.springframework.restdocs.payload.PayloadDocumentation.requestPartBody; import static org.springframework.restdocs.snippet.Attributes.attributes; @@ -41,89 +36,84 @@ * * @author Andy Wilkinson */ -public class RequestBodyPartSnippetTests extends AbstractSnippetTests { - - public RequestBodyPartSnippetTests(String name, TemplateFormat templateFormat) { - super(name, templateFormat); - } +class RequestBodyPartSnippetTests { - @Test - public void requestPartWithBody() throws IOException { + @RenderedSnippetTest + void requestPartWithBody(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException { requestPartBody("one") - .document(this.operationBuilder.request("http://localhost").part("one", "some content".getBytes()).build()); - assertThat(this.generatedSnippets.snippet("request-part-one-body")) - .is(codeBlock(null, "nowrap").withContent("some content")); + .document(operationBuilder.request("http://localhost").part("one", "some content".getBytes()).build()); + assertThat(snippets.requestPartBody("one")) + .isCodeBlock((codeBlock) -> codeBlock.withOptions("nowrap").content("some content")); } - @Test - public void requestPartWithNoBody() throws IOException { - requestPartBody("one") - .document(this.operationBuilder.request("http://localhost").part("one", new byte[0]).build()); - assertThat(this.generatedSnippets.snippet("request-part-one-body")) - .is(codeBlock(null, "nowrap").withContent("")); + @RenderedSnippetTest + void requestPartWithNoBody(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException { + requestPartBody("one").document(operationBuilder.request("http://localhost").part("one", new byte[0]).build()); + assertThat(snippets.requestPartBody("one")) + .isCodeBlock((codeBlock) -> codeBlock.withOptions("nowrap").content("")); } - @Test - public void requestPartWithJsonMediaType() throws IOException { - requestPartBody("one").document(this.operationBuilder.request("http://localhost") + @RenderedSnippetTest + void requestPartWithJsonMediaType(OperationBuilder operationBuilder, AssertableSnippets snippets) + throws IOException { + requestPartBody("one").document(operationBuilder.request("http://localhost") .part("one", "".getBytes()) .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) .build()); - assertThat(this.generatedSnippets.snippet("request-part-one-body")) - .is(codeBlock("json", "nowrap").withContent("")); + assertThat(snippets.requestPartBody("one")) + .isCodeBlock((codeBlock) -> codeBlock.withLanguageAndOptions("json", "nowrap").content("")); } - @Test - public void requestPartWithJsonSubtypeMediaType() throws IOException { - requestPartBody("one").document(this.operationBuilder.request("http://localhost") + @RenderedSnippetTest + void requestPartWithJsonSubtypeMediaType(OperationBuilder operationBuilder, AssertableSnippets snippets) + throws IOException { + requestPartBody("one").document(operationBuilder.request("http://localhost") .part("one", "".getBytes()) .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_PROBLEM_JSON_VALUE) .build()); - assertThat(this.generatedSnippets.snippet("request-part-one-body")) - .is(codeBlock("json", "nowrap").withContent("")); + assertThat(snippets.requestPartBody("one")) + .isCodeBlock((codeBlock) -> codeBlock.withLanguageAndOptions("json", "nowrap").content("")); } - @Test - public void requestPartWithXmlMediaType() throws IOException { - requestPartBody("one").document(this.operationBuilder.request("http://localhost") + @RenderedSnippetTest + void requestPartWithXmlMediaType(OperationBuilder operationBuilder, AssertableSnippets snippets) + throws IOException { + requestPartBody("one").document(operationBuilder.request("http://localhost") .part("one", "".getBytes()) .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_XML_VALUE) .build()); - assertThat(this.generatedSnippets.snippet("request-part-one-body")) - .is(codeBlock("xml", "nowrap").withContent("")); + assertThat(snippets.requestPartBody("one")) + .isCodeBlock((codeBlock) -> codeBlock.withLanguageAndOptions("xml", "nowrap").content("")); } - @Test - public void requestPartWithXmlSubtypeMediaType() throws IOException { - requestPartBody("one").document(this.operationBuilder.request("http://localhost") + @RenderedSnippetTest + void requestPartWithXmlSubtypeMediaType(OperationBuilder operationBuilder, AssertableSnippets snippets) + throws IOException { + requestPartBody("one").document(operationBuilder.request("http://localhost") .part("one", "".getBytes()) .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_ATOM_XML_VALUE) .build()); - assertThat(this.generatedSnippets.snippet("request-part-one-body")) - .is(codeBlock("xml", "nowrap").withContent("")); + assertThat(snippets.requestPartBody("one")) + .isCodeBlock((codeBlock) -> codeBlock.withLanguageAndOptions("xml", "nowrap").content("")); } - @Test - public void subsectionOfRequestPartBody() throws IOException { - requestPartBody("one", beneathPath("a.b")).document(this.operationBuilder.request("http://localhost") + @RenderedSnippetTest + void subsectionOfRequestPartBody(OperationBuilder operationBuilder, AssertableSnippets snippets) + throws IOException { + requestPartBody("one", beneathPath("a.b")).document(operationBuilder.request("http://localhost") .part("one", "{\"a\":{\"b\":{\"c\":5}}}".getBytes()) .build()); - assertThat(this.generatedSnippets.snippet("request-part-one-body-beneath-a.b")) - .is(codeBlock(null, "nowrap").withContent("{\"c\":5}")); + assertThat(snippets.requestPartBody("one", "beneath-a.b")) + .isCodeBlock((codeBlock) -> codeBlock.withOptions("nowrap").content("{\"c\":5}")); } - @Test - public void customSnippetAttributes() throws IOException { - TemplateResourceResolver resolver = mock(TemplateResourceResolver.class); - given(resolver.resolveTemplateResource("request-part-body")) - .willReturn(snippetResource("request-part-body-with-language")); - requestPartBody("one", attributes(key("language").value("json"))).document( - this.operationBuilder.attribute(TemplateEngine.class.getName(), new MustacheTemplateEngine(resolver)) - .request("http://localhost") - .part("one", "{\"a\":\"alpha\"}".getBytes()) - .build()); - assertThat(this.generatedSnippets.snippet("request-part-one-body")) - .is(codeBlock("json", "nowrap").withContent("{\"a\":\"alpha\"}")); + @RenderedSnippetTest + @SnippetTemplate(snippet = "request-part-body", template = "request-part-body-with-language") + void customSnippetAttributes(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException { + requestPartBody("one", attributes(key("language").value("json"))) + .document(operationBuilder.request("http://localhost").part("one", "{\"a\":\"alpha\"}".getBytes()).build()); + assertThat(snippets.requestPartBody("one")).isCodeBlock( + (codeBlock) -> codeBlock.withLanguageAndOptions("json", "nowrap").content("{\"a\":\"alpha\"}")); } } diff --git a/spring-restdocs-core/src/test/java/org/springframework/restdocs/payload/RequestBodySnippetTests.java b/spring-restdocs-core/src/test/java/org/springframework/restdocs/payload/RequestBodySnippetTests.java index 786e4299..44be030d 100644 --- a/spring-restdocs-core/src/test/java/org/springframework/restdocs/payload/RequestBodySnippetTests.java +++ b/spring-restdocs-core/src/test/java/org/springframework/restdocs/payload/RequestBodySnippetTests.java @@ -18,19 +18,14 @@ import java.io.IOException; -import org.junit.Test; - import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; -import org.springframework.restdocs.AbstractSnippetTests; -import org.springframework.restdocs.templates.TemplateEngine; -import org.springframework.restdocs.templates.TemplateFormat; -import org.springframework.restdocs.templates.TemplateResourceResolver; -import org.springframework.restdocs.templates.mustache.MustacheTemplateEngine; +import org.springframework.restdocs.testfixtures.jupiter.AssertableSnippets; +import org.springframework.restdocs.testfixtures.jupiter.OperationBuilder; +import org.springframework.restdocs.testfixtures.jupiter.RenderedSnippetTest; +import org.springframework.restdocs.testfixtures.jupiter.SnippetTemplate; import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.BDDMockito.given; -import static org.mockito.Mockito.mock; import static org.springframework.restdocs.payload.PayloadDocumentation.beneathPath; import static org.springframework.restdocs.payload.PayloadDocumentation.requestBody; import static org.springframework.restdocs.snippet.Attributes.attributes; @@ -41,77 +36,74 @@ * * @author Andy Wilkinson */ -public class RequestBodySnippetTests extends AbstractSnippetTests { - - public RequestBodySnippetTests(String name, TemplateFormat templateFormat) { - super(name, templateFormat); - } +class RequestBodySnippetTests { - @Test - public void requestWithBody() throws IOException { - requestBody().document(this.operationBuilder.request("http://localhost").content("some content").build()); - assertThat(this.generatedSnippets.snippet("request-body")) - .is(codeBlock(null, "nowrap").withContent("some content")); + @RenderedSnippetTest + void requestWithBody(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException { + requestBody().document(operationBuilder.request("http://localhost").content("some content").build()); + assertThat(snippets.requestBody()) + .isCodeBlock((codeBlock) -> codeBlock.withOptions("nowrap").content("some content")); } - @Test - public void requestWithNoBody() throws IOException { - requestBody().document(this.operationBuilder.request("http://localhost").build()); - assertThat(this.generatedSnippets.snippet("request-body")).is(codeBlock(null, "nowrap").withContent("")); + @RenderedSnippetTest + void requestWithNoBody(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException { + requestBody().document(operationBuilder.request("http://localhost").build()); + assertThat(snippets.requestBody()).isCodeBlock((codeBlock) -> codeBlock.withOptions("nowrap").content("")); } - @Test - public void requestWithJsonMediaType() throws IOException { - requestBody().document(this.operationBuilder.request("http://localhost") + @RenderedSnippetTest + void requestWithJsonMediaType(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException { + requestBody().document(operationBuilder.request("http://localhost") .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) .build()); - assertThat(this.generatedSnippets.snippet("request-body")).is(codeBlock("json", "nowrap").withContent("")); + assertThat(snippets.requestBody()) + .isCodeBlock((codeBlock) -> codeBlock.withLanguageAndOptions("json", "nowrap").content("")); } - @Test - public void requestWithJsonSubtypeMediaType() throws IOException { - requestBody().document(this.operationBuilder.request("http://localhost") + @RenderedSnippetTest + void requestWithJsonSubtypeMediaType(OperationBuilder operationBuilder, AssertableSnippets snippets) + throws IOException { + requestBody().document(operationBuilder.request("http://localhost") .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_PROBLEM_JSON_VALUE) .build()); - assertThat(this.generatedSnippets.snippet("request-body")).is(codeBlock("json", "nowrap").withContent("")); + assertThat(snippets.requestBody()) + .isCodeBlock((codeBlock) -> codeBlock.withLanguageAndOptions("json", "nowrap").content("")); } - @Test - public void requestWithXmlMediaType() throws IOException { - requestBody().document(this.operationBuilder.request("http://localhost") + @RenderedSnippetTest + void requestWithXmlMediaType(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException { + requestBody().document(operationBuilder.request("http://localhost") .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_XML_VALUE) .build()); - assertThat(this.generatedSnippets.snippet("request-body")).is(codeBlock("xml", "nowrap").withContent("")); + assertThat(snippets.requestBody()) + .isCodeBlock((codeBlock) -> codeBlock.withLanguageAndOptions("xml", "nowrap").content("")); } - @Test - public void requestWithXmlSubtypeMediaType() throws IOException { - requestBody().document(this.operationBuilder.request("http://localhost") + @RenderedSnippetTest + void requestWithXmlSubtypeMediaType(OperationBuilder operationBuilder, AssertableSnippets snippets) + throws IOException { + requestBody().document(operationBuilder.request("http://localhost") .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_ATOM_XML_VALUE) .build()); - assertThat(this.generatedSnippets.snippet("request-body")).is(codeBlock("xml", "nowrap").withContent("")); + assertThat(snippets.requestBody()) + .isCodeBlock((codeBlock) -> codeBlock.withLanguageAndOptions("xml", "nowrap").content("")); } - @Test - public void subsectionOfRequestBody() throws IOException { + @RenderedSnippetTest + void subsectionOfRequestBody(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException { requestBody(beneathPath("a.b")) - .document(this.operationBuilder.request("http://localhost").content("{\"a\":{\"b\":{\"c\":5}}}").build()); - assertThat(this.generatedSnippets.snippet("request-body-beneath-a.b")) - .is(codeBlock(null, "nowrap").withContent("{\"c\":5}")); + .document(operationBuilder.request("http://localhost").content("{\"a\":{\"b\":{\"c\":5}}}").build()); + assertThat(snippets.requestBody("beneath-a.b")) + .isCodeBlock((codeBlock) -> codeBlock.withOptions("nowrap").content("{\"c\":5}")); } - @Test - public void customSnippetAttributes() throws IOException { - TemplateResourceResolver resolver = mock(TemplateResourceResolver.class); - given(resolver.resolveTemplateResource("request-body")) - .willReturn(snippetResource("request-body-with-language")); - requestBody(attributes(key("language").value("json"))).document( - this.operationBuilder.attribute(TemplateEngine.class.getName(), new MustacheTemplateEngine(resolver)) - .request("http://localhost") - .content("{\"a\":\"alpha\"}") - .build()); - assertThat(this.generatedSnippets.snippet("request-body")) - .is(codeBlock("json", "nowrap").withContent("{\"a\":\"alpha\"}")); + @RenderedSnippetTest + @SnippetTemplate(snippet = "request-body", template = "request-body-with-language") + void customSnippetAttributes(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException { + requestBody(attributes(key("language").value("json"))) + .document(operationBuilder.request("http://localhost").content("{\"a\":\"alpha\"}").build()); + assertThat(snippets.requestBody()).isCodeBlock( + (codeBlock) -> codeBlock.withLanguageAndOptions("json", "nowrap").content("{\"a\":\"alpha\"}")); } } diff --git a/spring-restdocs-core/src/test/java/org/springframework/restdocs/payload/RequestFieldsSnippetFailureTests.java b/spring-restdocs-core/src/test/java/org/springframework/restdocs/payload/RequestFieldsSnippetFailureTests.java deleted file mode 100644 index b7a8da7e..00000000 --- a/spring-restdocs-core/src/test/java/org/springframework/restdocs/payload/RequestFieldsSnippetFailureTests.java +++ /dev/null @@ -1,196 +0,0 @@ -/* - * Copyright 2014-present 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.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.emptyList()) - .document(this.operationBuilder.request("http://localhost") - .content("5") - .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("5") - .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("5") - .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("") - .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("5") - .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.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]"); - } - -} diff --git a/spring-restdocs-core/src/test/java/org/springframework/restdocs/payload/RequestFieldsSnippetTests.java b/spring-restdocs-core/src/test/java/org/springframework/restdocs/payload/RequestFieldsSnippetTests.java index 8958df57..8674406d 100644 --- a/spring-restdocs-core/src/test/java/org/springframework/restdocs/payload/RequestFieldsSnippetTests.java +++ b/spring-restdocs-core/src/test/java/org/springframework/restdocs/payload/RequestFieldsSnippetTests.java @@ -18,21 +18,19 @@ import java.io.IOException; import java.util.Arrays; - -import org.junit.Test; +import java.util.Collections; import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; -import org.springframework.restdocs.AbstractSnippetTests; -import org.springframework.restdocs.templates.TemplateEngine; -import org.springframework.restdocs.templates.TemplateFormat; -import org.springframework.restdocs.templates.TemplateFormats; -import org.springframework.restdocs.templates.TemplateResourceResolver; -import org.springframework.restdocs.templates.mustache.MustacheTemplateEngine; +import org.springframework.restdocs.snippet.SnippetException; +import org.springframework.restdocs.testfixtures.jupiter.AssertableSnippets; +import org.springframework.restdocs.testfixtures.jupiter.OperationBuilder; +import org.springframework.restdocs.testfixtures.jupiter.RenderedSnippetTest; +import org.springframework.restdocs.testfixtures.jupiter.SnippetTemplate; +import org.springframework.restdocs.testfixtures.jupiter.SnippetTest; import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.BDDMockito.given; -import static org.mockito.Mockito.mock; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.springframework.restdocs.payload.PayloadDocumentation.beneathPath; import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath; import static org.springframework.restdocs.payload.PayloadDocumentation.requestFields; @@ -46,342 +44,492 @@ * @author Andy Wilkinson * @author Sungjun Lee */ -public class RequestFieldsSnippetTests extends AbstractSnippetTests { - - public RequestFieldsSnippetTests(String name, TemplateFormat templateFormat) { - super(name, templateFormat); - } +class RequestFieldsSnippetTests { - @Test - public void mapRequestWithFields() throws IOException { + @RenderedSnippetTest + void mapRequestWithFields(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException { new RequestFieldsSnippet(Arrays.asList(fieldWithPath("a.b").description("one"), fieldWithPath("a.c").description("two"), fieldWithPath("a").description("three"))) - .document(this.operationBuilder.request("http://localhost") + .document(operationBuilder.request("http://localhost") .content("{\"a\": {\"b\": 5, \"c\": \"charlie\"}}") .build()); - assertThat(this.generatedSnippets.requestFields()) - .is(tableWithHeader("Path", "Type", "Description").row("`a.b`", "`Number`", "one") - .row("`a.c`", "`String`", "two") - .row("`a`", "`Object`", "three")); + assertThat(snippets.requestFields()).isTable((table) -> table.withHeader("Path", "Type", "Description") + .row("`a.b`", "`Number`", "one") + .row("`a.c`", "`String`", "two") + .row("`a`", "`Object`", "three")); } - @Test - public void mapRequestWithNullField() throws IOException { + @RenderedSnippetTest + void mapRequestWithNullField(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException { new RequestFieldsSnippet(Arrays.asList(fieldWithPath("a.b").description("one"))) - .document(this.operationBuilder.request("http://localhost").content("{\"a\": {\"b\": null}}").build()); - assertThat(this.generatedSnippets.requestFields()) - .is(tableWithHeader("Path", "Type", "Description").row("`a.b`", "`Null`", "one")); + .document(operationBuilder.request("http://localhost").content("{\"a\": {\"b\": null}}").build()); + assertThat(snippets.requestFields()) + .isTable((table) -> table.withHeader("Path", "Type", "Description").row("`a.b`", "`Null`", "one")); } - @Test - public void entireSubsectionsCanBeDocumented() throws IOException { + @RenderedSnippetTest + void entireSubsectionsCanBeDocumented(OperationBuilder operationBuilder, AssertableSnippets snippets) + throws IOException { new RequestFieldsSnippet(Arrays.asList(subsectionWithPath("a").description("one"))) - .document(this.operationBuilder.request("http://localhost") + .document(operationBuilder.request("http://localhost") .content("{\"a\": {\"b\": 5, \"c\": \"charlie\"}}") .build()); - assertThat(this.generatedSnippets.requestFields()) - .is(tableWithHeader("Path", "Type", "Description").row("`a`", "`Object`", "one")); + assertThat(snippets.requestFields()) + .isTable((table) -> table.withHeader("Path", "Type", "Description").row("`a`", "`Object`", "one")); } - @Test - public void subsectionOfMapRequest() throws IOException { + @RenderedSnippetTest + void subsectionOfMapRequest(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException { requestFields(beneathPath("a"), fieldWithPath("b").description("one"), fieldWithPath("c").description("two")) - .document(this.operationBuilder.request("http://localhost") + .document(operationBuilder.request("http://localhost") .content("{\"a\": {\"b\": 5, \"c\": \"charlie\"}}") .build()); - assertThat(this.generatedSnippets.snippet("request-fields-beneath-a")) - .is(tableWithHeader("Path", "Type", "Description").row("`b`", "`Number`", "one") + assertThat(snippets.requestFields("beneath-a")) + .isTable((table) -> table.withHeader("Path", "Type", "Description") + .row("`b`", "`Number`", "one") .row("`c`", "`String`", "two")); } - @Test - public void subsectionOfMapRequestWithCommonPrefix() throws IOException { + @RenderedSnippetTest + void subsectionOfMapRequestWithCommonPrefix(OperationBuilder operationBuilder, AssertableSnippets snippets) + throws IOException { requestFields(beneathPath("a")).andWithPrefix("b.", fieldWithPath("c").description("two")) - .document(this.operationBuilder.request("http://localhost") + .document(operationBuilder.request("http://localhost") .content("{\"a\": {\"b\": {\"c\": \"charlie\"}}}") .build()); - assertThat(this.generatedSnippets.snippet("request-fields-beneath-a")) - .is(tableWithHeader("Path", "Type", "Description").row("`b.c`", "`String`", "two")); + assertThat(snippets.requestFields("beneath-a")) + .isTable((table) -> table.withHeader("Path", "Type", "Description").row("`b.c`", "`String`", "two")); } - @Test - public void arrayRequestWithFields() throws IOException { + @RenderedSnippetTest + void arrayRequestWithFields(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException { new RequestFieldsSnippet( Arrays.asList(fieldWithPath("[]").description("one"), fieldWithPath("[]a.b").description("two"), fieldWithPath("[]a.c").description("three"), fieldWithPath("[]a").description("four"))) - .document(this.operationBuilder.request("http://localhost") + .document(operationBuilder.request("http://localhost") .content("[{\"a\": {\"b\": 5, \"c\":\"charlie\"}}," + "{\"a\": {\"b\": 4, \"c\":\"chalk\"}}]") .build()); - assertThat(this.generatedSnippets.requestFields()) - .is(tableWithHeader("Path", "Type", "Description").row("`[]`", "`Array`", "one") - .row("`[]a.b`", "`Number`", "two") - .row("`[]a.c`", "`String`", "three") - .row("`[]a`", "`Object`", "four")); + assertThat(snippets.requestFields()).isTable((table) -> table.withHeader("Path", "Type", "Description") + .row("`[]`", "`Array`", "one") + .row("`[]a.b`", "`Number`", "two") + .row("`[]a.c`", "`String`", "three") + .row("`[]a`", "`Object`", "four")); } - @Test - public void arrayRequestWithAlwaysNullField() throws IOException { + @RenderedSnippetTest + void arrayRequestWithAlwaysNullField(OperationBuilder operationBuilder, AssertableSnippets snippets) + throws IOException { new RequestFieldsSnippet(Arrays.asList(fieldWithPath("[]a.b").description("one"))) - .document(this.operationBuilder.request("http://localhost") + .document(operationBuilder.request("http://localhost") .content("[{\"a\": {\"b\": null}}," + "{\"a\": {\"b\": null}}]") .build()); - assertThat(this.generatedSnippets.requestFields()) - .is(tableWithHeader("Path", "Type", "Description").row("`[]a.b`", "`Null`", "one")); + assertThat(snippets.requestFields()) + .isTable((table) -> table.withHeader("Path", "Type", "Description").row("`[]a.b`", "`Null`", "one")); } - @Test - public void subsectionOfArrayRequest() throws IOException { + @RenderedSnippetTest + void subsectionOfArrayRequest(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException { requestFields(beneathPath("[].a"), fieldWithPath("b").description("one"), fieldWithPath("c").description("two")) - .document(this.operationBuilder.request("http://localhost") + .document(operationBuilder.request("http://localhost") .content("[{\"a\": {\"b\": 5, \"c\": \"charlie\"}}]") .build()); - assertThat(this.generatedSnippets.snippet("request-fields-beneath-[].a")) - .is(tableWithHeader("Path", "Type", "Description").row("`b`", "`Number`", "one") + assertThat(snippets.requestFields("beneath-[].a")) + .isTable((table) -> table.withHeader("Path", "Type", "Description") + .row("`b`", "`Number`", "one") .row("`c`", "`String`", "two")); } - @Test - public void ignoredRequestField() throws IOException { + @RenderedSnippetTest + void ignoredRequestField(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException { new RequestFieldsSnippet(Arrays.asList(fieldWithPath("a").ignored(), fieldWithPath("b").description("Field b"))) - .document(this.operationBuilder.request("http://localhost").content("{\"a\": 5, \"b\": 4}").build()); - assertThat(this.generatedSnippets.requestFields()) - .is(tableWithHeader("Path", "Type", "Description").row("`b`", "`Number`", "Field b")); + .document(operationBuilder.request("http://localhost").content("{\"a\": 5, \"b\": 4}").build()); + assertThat(snippets.requestFields()) + .isTable((table) -> table.withHeader("Path", "Type", "Description").row("`b`", "`Number`", "Field b")); } - @Test - public void entireSubsectionCanBeIgnored() throws IOException { + @RenderedSnippetTest + void entireSubsectionCanBeIgnored(OperationBuilder operationBuilder, AssertableSnippets snippets) + throws IOException { new RequestFieldsSnippet( Arrays.asList(subsectionWithPath("a").ignored(), fieldWithPath("c").description("Field c"))) - .document( - this.operationBuilder.request("http://localhost").content("{\"a\": {\"b\": 5}, \"c\": 4}").build()); - assertThat(this.generatedSnippets.requestFields()) - .is(tableWithHeader("Path", "Type", "Description").row("`c`", "`Number`", "Field c")); + .document(operationBuilder.request("http://localhost").content("{\"a\": {\"b\": 5}, \"c\": 4}").build()); + assertThat(snippets.requestFields()) + .isTable((table) -> table.withHeader("Path", "Type", "Description").row("`c`", "`Number`", "Field c")); } - @Test - public void allUndocumentedRequestFieldsCanBeIgnored() throws IOException { + @RenderedSnippetTest + void allUndocumentedRequestFieldsCanBeIgnored(OperationBuilder operationBuilder, AssertableSnippets snippets) + throws IOException { new RequestFieldsSnippet(Arrays.asList(fieldWithPath("b").description("Field b")), true) - .document(this.operationBuilder.request("http://localhost").content("{\"a\": 5, \"b\": 4}").build()); - assertThat(this.generatedSnippets.requestFields()) - .is(tableWithHeader("Path", "Type", "Description").row("`b`", "`Number`", "Field b")); + .document(operationBuilder.request("http://localhost").content("{\"a\": 5, \"b\": 4}").build()); + assertThat(snippets.requestFields()) + .isTable((table) -> table.withHeader("Path", "Type", "Description").row("`b`", "`Number`", "Field b")); } - @Test - public void allUndocumentedFieldsContinueToBeIgnoredAfterAddingDescriptors() throws IOException { + @RenderedSnippetTest + void allUndocumentedFieldsContinueToBeIgnoredAfterAddingDescriptors(OperationBuilder operationBuilder, + AssertableSnippets snippets) throws IOException { new RequestFieldsSnippet(Arrays.asList(fieldWithPath("b").description("Field b")), true) .andWithPrefix("c.", fieldWithPath("d").description("Field d")) - .document(this.operationBuilder.request("http://localhost") - .content("{\"a\":5,\"b\":4,\"c\":{\"d\": 3}}") - .build()); - assertThat(this.generatedSnippets.requestFields()) - .is(tableWithHeader("Path", "Type", "Description").row("`b`", "`Number`", "Field b") - .row("`c.d`", "`Number`", "Field d")); + .document( + operationBuilder.request("http://localhost").content("{\"a\":5,\"b\":4,\"c\":{\"d\": 3}}").build()); + assertThat(snippets.requestFields()).isTable((table) -> table.withHeader("Path", "Type", "Description") + .row("`b`", "`Number`", "Field b") + .row("`c.d`", "`Number`", "Field d")); } - @Test - public void missingOptionalRequestField() throws IOException { + @RenderedSnippetTest + void missingOptionalRequestField(OperationBuilder operationBuilder, AssertableSnippets snippets) + 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")); + .document(operationBuilder.request("http://localhost").content("{}").build()); + assertThat(snippets.requestFields()) + .isTable((table) -> table.withHeader("Path", "Type", "Description").row("`a.b`", "`String`", "one")); } - @Test - public void missingIgnoredOptionalRequestFieldDoesNotRequireAType() throws IOException { + @RenderedSnippetTest + void missingIgnoredOptionalRequestFieldDoesNotRequireAType(OperationBuilder operationBuilder, + AssertableSnippets snippets) 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")); + .document(operationBuilder.request("http://localhost").content("{}").build()); + assertThat(snippets.requestFields()).isTable((table) -> table.withHeader("Path", "Type", "Description")); } - @Test - public void presentOptionalRequestField() throws IOException { + @RenderedSnippetTest + void presentOptionalRequestField(OperationBuilder operationBuilder, AssertableSnippets snippets) + throws IOException { new RequestFieldsSnippet( Arrays.asList(fieldWithPath("a.b").description("one").type(JsonFieldType.STRING).optional())) - .document( - this.operationBuilder.request("http://localhost").content("{\"a\": { \"b\": \"bravo\"}}").build()); - assertThat(this.generatedSnippets.requestFields()) - .is(tableWithHeader("Path", "Type", "Description").row("`a.b`", "`String`", "one")); + .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")); } - @Test - public void requestFieldsWithCustomAttributes() throws IOException { - TemplateResourceResolver resolver = mock(TemplateResourceResolver.class); - given(resolver.resolveTemplateResource("request-fields")) - .willReturn(snippetResource("request-fields-with-title")); + @RenderedSnippetTest + @SnippetTemplate(snippet = "request-fields", template = "request-fields-with-title") + void requestFieldsWithCustomAttributes(OperationBuilder operationBuilder, AssertableSnippets snippets) + throws IOException { new RequestFieldsSnippet(Arrays.asList(fieldWithPath("a").description("one")), attributes(key("title").value("Custom title"))) - .document(this.operationBuilder - .attribute(TemplateEngine.class.getName(), new MustacheTemplateEngine(resolver)) - .request("http://localhost") - .content("{\"a\": \"foo\"}") - .build()); - assertThat(this.generatedSnippets.requestFields()).contains("Custom title"); + .document(operationBuilder.request("http://localhost").content("{\"a\": \"foo\"}").build()); + assertThat(snippets.requestFields()) + .isTable((table) -> table.withTitleAndHeader("Custom title", "Path", "Type", "Description") + .row("a", "String", "one")); } - @Test - public void requestFieldsWithCustomDescriptorAttributes() throws IOException { - TemplateResourceResolver resolver = mock(TemplateResourceResolver.class); - given(resolver.resolveTemplateResource("request-fields")) - .willReturn(snippetResource("request-fields-with-extra-column")); + @RenderedSnippetTest + @SnippetTemplate(snippet = "request-fields", template = "request-fields-with-extra-column") + void requestFieldsWithCustomDescriptorAttributes(OperationBuilder operationBuilder, AssertableSnippets snippets) + throws IOException { new RequestFieldsSnippet( Arrays.asList(fieldWithPath("a.b").description("one").attributes(key("foo").value("alpha")), fieldWithPath("a.c").description("two").attributes(key("foo").value("bravo")), fieldWithPath("a").description("three").attributes(key("foo").value("charlie")))) - .document(this.operationBuilder - .attribute(TemplateEngine.class.getName(), new MustacheTemplateEngine(resolver)) - .request("http://localhost") + .document(operationBuilder.request("http://localhost") .content("{\"a\": {\"b\": 5, \"c\": \"charlie\"}}") .build()); - assertThat(this.generatedSnippets.requestFields()) - .is(tableWithHeader("Path", "Type", "Description", "Foo").row("a.b", "Number", "one", "alpha") - .row("a.c", "String", "two", "bravo") - .row("a", "Object", "three", "charlie")); + assertThat(snippets.requestFields()).isTable((table) -> table.withHeader("Path", "Type", "Description", "Foo") + .row("a.b", "Number", "one", "alpha") + .row("a.c", "String", "two", "bravo") + .row("a", "Object", "three", "charlie")); } - @Test - public void fieldWithExplicitExactlyMatchingType() throws IOException { + @RenderedSnippetTest + void fieldWithExplicitExactlyMatchingType(OperationBuilder operationBuilder, AssertableSnippets snippets) + throws IOException { new RequestFieldsSnippet(Arrays.asList(fieldWithPath("a").description("one").type(JsonFieldType.NUMBER))) - .document(this.operationBuilder.request("http://localhost").content("{\"a\": 5 }").build()); - assertThat(this.generatedSnippets.requestFields()) - .is(tableWithHeader("Path", "Type", "Description").row("`a`", "`Number`", "one")); + .document(operationBuilder.request("http://localhost").content("{\"a\": 5 }").build()); + assertThat(snippets.requestFields()) + .isTable((table) -> table.withHeader("Path", "Type", "Description").row("`a`", "`Number`", "one")); } - @Test - public void fieldWithExplicitVariesType() throws IOException { + @RenderedSnippetTest + void fieldWithExplicitVariesType(OperationBuilder operationBuilder, AssertableSnippets snippets) + throws IOException { new RequestFieldsSnippet(Arrays.asList(fieldWithPath("a").description("one").type(JsonFieldType.VARIES))) - .document(this.operationBuilder.request("http://localhost").content("{\"a\": 5 }").build()); - assertThat(this.generatedSnippets.requestFields()) - .is(tableWithHeader("Path", "Type", "Description").row("`a`", "`Varies`", "one")); + .document(operationBuilder.request("http://localhost").content("{\"a\": 5 }").build()); + assertThat(snippets.requestFields()) + .isTable((table) -> table.withHeader("Path", "Type", "Description").row("`a`", "`Varies`", "one")); } - @Test - public void applicationXmlRequestFields() throws IOException { - xmlRequestFields(MediaType.APPLICATION_XML); + @RenderedSnippetTest + void applicationXmlRequestFields(OperationBuilder operationBuilder, AssertableSnippets snippets) + throws IOException { + xmlRequestFields(MediaType.APPLICATION_XML, operationBuilder, snippets); } - @Test - public void textXmlRequestFields() throws IOException { - xmlRequestFields(MediaType.TEXT_XML); + @RenderedSnippetTest + void textXmlRequestFields(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException { + xmlRequestFields(MediaType.TEXT_XML, operationBuilder, snippets); } - @Test - public void customXmlRequestFields() throws IOException { - xmlRequestFields(MediaType.parseMediaType("application/vnd.com.example+xml")); + @RenderedSnippetTest + void customXmlRequestFields(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException { + xmlRequestFields(MediaType.parseMediaType("application/vnd.com.example+xml"), operationBuilder, snippets); } - private void xmlRequestFields(MediaType contentType) throws IOException { + private void xmlRequestFields(MediaType contentType, OperationBuilder operationBuilder, AssertableSnippets snippets) + throws IOException { new RequestFieldsSnippet(Arrays.asList(fieldWithPath("a/b").description("one").type("b"), fieldWithPath("a/c").description("two").type("c"), fieldWithPath("a").description("three").type("a"))) - .document(this.operationBuilder.request("http://localhost") + .document(operationBuilder.request("http://localhost") .content("5charlie") .header(HttpHeaders.CONTENT_TYPE, contentType.toString()) .build()); - assertThat(this.generatedSnippets.requestFields()) - .is(tableWithHeader("Path", "Type", "Description").row("`a/b`", "`b`", "one") - .row("`a/c`", "`c`", "two") - .row("`a`", "`a`", "three")); + assertThat(snippets.requestFields()).isTable((table) -> table.withHeader("Path", "Type", "Description") + .row("`a/b`", "`b`", "one") + .row("`a/c`", "`c`", "two") + .row("`a`", "`a`", "three")); } - @Test - public void entireSubsectionOfXmlPayloadCanBeDocumented() throws IOException { + @RenderedSnippetTest + void entireSubsectionOfXmlPayloadCanBeDocumented(OperationBuilder operationBuilder, AssertableSnippets snippets) + throws IOException { new RequestFieldsSnippet(Arrays.asList(subsectionWithPath("a").description("one").type("a"))) - .document(this.operationBuilder.request("http://localhost") + .document(operationBuilder.request("http://localhost") .content("5charlie") .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_XML_VALUE) .build()); - assertThat(this.generatedSnippets.requestFields()) - .is(tableWithHeader("Path", "Type", "Description").row("`a`", "`a`", "one")); + assertThat(snippets.requestFields()) + .isTable((table) -> table.withHeader("Path", "Type", "Description").row("`a`", "`a`", "one")); } - @Test - public void additionalDescriptors() throws IOException { + @RenderedSnippetTest + void additionalDescriptors(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException { PayloadDocumentation .requestFields(fieldWithPath("a.b").description("one"), fieldWithPath("a.c").description("two")) .and(fieldWithPath("a").description("three")) - .document(this.operationBuilder.request("http://localhost") + .document(operationBuilder.request("http://localhost") .content("{\"a\": {\"b\": 5, \"c\": \"charlie\"}}") .build()); - assertThat(this.generatedSnippets.requestFields()) - .is(tableWithHeader("Path", "Type", "Description").row("`a.b`", "`Number`", "one") - .row("`a.c`", "`String`", "two") - .row("`a`", "`Object`", "three")); + assertThat(snippets.requestFields()).isTable((table) -> table.withHeader("Path", "Type", "Description") + .row("`a.b`", "`Number`", "one") + .row("`a.c`", "`String`", "two") + .row("`a`", "`Object`", "three")); } - @Test - public void prefixedAdditionalDescriptors() throws IOException { + @RenderedSnippetTest + void prefixedAdditionalDescriptors(OperationBuilder operationBuilder, AssertableSnippets snippets) + throws IOException { PayloadDocumentation.requestFields(fieldWithPath("a").description("one")) .andWithPrefix("a.", fieldWithPath("b").description("two"), fieldWithPath("c").description("three")) - .document(this.operationBuilder.request("http://localhost") + .document(operationBuilder.request("http://localhost") .content("{\"a\": {\"b\": 5, \"c\": \"charlie\"}}") .build()); - assertThat(this.generatedSnippets.requestFields()) - .is(tableWithHeader("Path", "Type", "Description").row("`a`", "`Object`", "one") - .row("`a.b`", "`Number`", "two") - .row("`a.c`", "`String`", "three")); + assertThat(snippets.requestFields()).isTable((table) -> table.withHeader("Path", "Type", "Description") + .row("`a`", "`Object`", "one") + .row("`a.b`", "`Number`", "two") + .row("`a.c`", "`String`", "three")); } - @Test - public void requestWithFieldsWithEscapedContent() throws IOException { + @RenderedSnippetTest + void requestWithFieldsWithEscapedContent(OperationBuilder operationBuilder, AssertableSnippets snippets) + throws IOException { new RequestFieldsSnippet(Arrays.asList(fieldWithPath("Foo|Bar").type("one|two").description("three|four"))) - .document(this.operationBuilder.request("http://localhost").content("{\"Foo|Bar\": 5}").build()); - assertThat(this.generatedSnippets.requestFields()).is(tableWithHeader("Path", "Type", "Description") - .row(escapeIfNecessary("`Foo|Bar`"), escapeIfNecessary("`one|two`"), escapeIfNecessary("three|four"))); + .document(operationBuilder.request("http://localhost").content("{\"Foo|Bar\": 5}").build()); + assertThat(snippets.requestFields()).isTable( + (table) -> table.withHeader("Path", "Type", "Description").row("`Foo|Bar`", "`one|two`", "three|four")); } - @Test - public void mapRequestWithVaryingKeysMatchedUsingWildcard() throws IOException { + @RenderedSnippetTest + void mapRequestWithVaryingKeysMatchedUsingWildcard(OperationBuilder operationBuilder, AssertableSnippets snippets) + throws IOException { new RequestFieldsSnippet(Arrays.asList(fieldWithPath("things.*.size").description("one"), fieldWithPath("things.*.type").description("two"))) - .document(this.operationBuilder.request("http://localhost") + .document(operationBuilder.request("http://localhost") .content("{\"things\": {\"12abf\": {\"type\":" + "\"Whale\", \"size\": \"HUGE\"}," + "\"gzM33\" : {\"type\": \"Screw\"," + "\"size\": \"SMALL\"}}}") .build()); - assertThat(this.generatedSnippets.requestFields()) - .is(tableWithHeader("Path", "Type", "Description").row("`things.*.size`", "`String`", "one") - .row("`things.*.type`", "`String`", "two")); + assertThat(snippets.requestFields()).isTable((table) -> table.withHeader("Path", "Type", "Description") + .row("`things.*.size`", "`String`", "one") + .row("`things.*.type`", "`String`", "two")); } - @Test - public void requestWithArrayContainingFieldThatIsSometimesNull() throws IOException { + @RenderedSnippetTest + void requestWithArrayContainingFieldThatIsSometimesNull(OperationBuilder operationBuilder, + AssertableSnippets snippets) throws IOException { new RequestFieldsSnippet( Arrays.asList(fieldWithPath("assets[].name").description("one").type(JsonFieldType.STRING).optional())) - .document(this.operationBuilder.request("http://localhost") + .document(operationBuilder.request("http://localhost") .content("{\"assets\": [" + "{\"name\": \"sample1\"}, " + "{\"name\": null}, " + "{\"name\": \"sample2\"}]}") .build()); - assertThat(this.generatedSnippets.requestFields()) - .is(tableWithHeader("Path", "Type", "Description").row("`assets[].name`", "`String`", "one")); + assertThat(snippets.requestFields()).isTable( + (table) -> table.withHeader("Path", "Type", "Description").row("`assets[].name`", "`String`", "one")); } - @Test - public void optionalFieldBeneathArrayThatIsSometimesAbsent() throws IOException { + @RenderedSnippetTest + void optionalFieldBeneathArrayThatIsSometimesAbsent(OperationBuilder operationBuilder, AssertableSnippets snippets) + throws IOException { new RequestFieldsSnippet( Arrays.asList(fieldWithPath("a[].b").description("one").type(JsonFieldType.NUMBER).optional(), fieldWithPath("a[].c").description("two").type(JsonFieldType.NUMBER))) - .document(this.operationBuilder.request("http://localhost") + .document(operationBuilder.request("http://localhost") .content("{\"a\":[{\"b\": 1,\"c\": 2}, " + "{\"c\": 2}, {\"b\": 1,\"c\": 2}]}") .build()); - assertThat(this.generatedSnippets.requestFields()) - .is(tableWithHeader("Path", "Type", "Description").row("`a[].b`", "`Number`", "one") - .row("`a[].c`", "`Number`", "two")); + assertThat(snippets.requestFields()).isTable((table) -> table.withHeader("Path", "Type", "Description") + .row("`a[].b`", "`Number`", "one") + .row("`a[].c`", "`Number`", "two")); } - @Test - public void typeDeterminationDoesNotSetTypeOnDescriptor() throws IOException { + @RenderedSnippetTest + void typeDeterminationDoesNotSetTypeOnDescriptor(OperationBuilder operationBuilder, AssertableSnippets snippets) + throws IOException { FieldDescriptor descriptor = fieldWithPath("a.b").description("one"); new RequestFieldsSnippet(Arrays.asList(descriptor)) - .document(this.operationBuilder.request("http://localhost").content("{\"a\": {\"b\": 5}}").build()); + .document(operationBuilder.request("http://localhost").content("{\"a\": {\"b\": 5}}").build()); assertThat(descriptor.getType()).isNull(); - assertThat(this.generatedSnippets.requestFields()) - .is(tableWithHeader("Path", "Type", "Description").row("`a.b`", "`Number`", "one")); - } - - private String escapeIfNecessary(String input) { - if (this.templateFormat.getId().equals(TemplateFormats.markdown().getId())) { - return input; - } - return input.replace("|", "\\|"); + assertThat(snippets.requestFields()) + .isTable((table) -> table.withHeader("Path", "Type", "Description").row("`a.b`", "`Number`", "one")); + } + + @SnippetTest + void undocumentedRequestField(OperationBuilder operationBuilder) { + assertThatExceptionOfType(SnippetException.class) + .isThrownBy(() -> new RequestFieldsSnippet(Collections.emptyList()) + .document(operationBuilder.request("http://localhost").content("{\"a\": 5}").build())) + .withMessageStartingWith("The following parts of the payload were not documented:"); + } + + @SnippetTest + void missingRequestField(OperationBuilder operationBuilder) { + assertThatExceptionOfType(SnippetException.class) + .isThrownBy(() -> new RequestFieldsSnippet(Arrays.asList(fieldWithPath("a.b").description("one"))) + .document(operationBuilder.request("http://localhost").content("{}").build())) + .withMessage("Fields with the following paths were not found in the payload: [a.b]"); + } + + @SnippetTest + void missingOptionalRequestFieldWithNoTypeProvided(OperationBuilder operationBuilder) { + assertThatExceptionOfType(FieldTypeRequiredException.class).isThrownBy( + () -> new RequestFieldsSnippet(Arrays.asList(fieldWithPath("a.b").description("one").optional())) + .document(operationBuilder.request("http://localhost").content("{ }").build())); + } + + @SnippetTest + void undocumentedRequestFieldAndMissingRequestField(OperationBuilder operationBuilder) { + assertThatExceptionOfType(SnippetException.class) + .isThrownBy(() -> new RequestFieldsSnippet(Arrays.asList(fieldWithPath("a.b").description("one"))) + .document(operationBuilder.request("http://localhost").content("{ \"a\": { \"c\": 5 }}").build())) + .withMessageStartingWith("The following parts of the payload were not documented:") + .withMessageEndingWith("Fields with the following paths were not found in the payload: [a.b]"); + } + + @SnippetTest + void attemptToDocumentFieldsWithNoRequestBody(OperationBuilder operationBuilder) { + assertThatExceptionOfType(SnippetException.class) + .isThrownBy(() -> new RequestFieldsSnippet(Arrays.asList(fieldWithPath("a").description("one"))) + .document(operationBuilder.request("http://localhost").build())) + .withMessage("Cannot document request fields as the request body is empty"); + } + + @SnippetTest + void fieldWithExplicitTypeThatDoesNotMatchThePayload(OperationBuilder operationBuilder) { + assertThatExceptionOfType(FieldTypesDoNotMatchException.class) + .isThrownBy(() -> new RequestFieldsSnippet( + Arrays.asList(fieldWithPath("a").description("one").type(JsonFieldType.OBJECT))) + .document(operationBuilder.request("http://localhost").content("{ \"a\": 5 }").build())) + .withMessage("The documented type of the field 'a' is Object but the actual type is Number"); + } + + @SnippetTest + void fieldWithExplicitSpecificTypeThatActuallyVaries(OperationBuilder operationBuilder) { + assertThatExceptionOfType(FieldTypesDoNotMatchException.class).isThrownBy(() -> new RequestFieldsSnippet( + Arrays.asList(fieldWithPath("[].a").description("one").type(JsonFieldType.OBJECT))) + .document(operationBuilder.request("http://localhost").content("[{ \"a\": 5 },{ \"a\": \"b\" }]").build())) + .withMessage("The documented type of the field '[].a' is Object but the actual type is Varies"); + } + + @SnippetTest + void undocumentedXmlRequestField(OperationBuilder operationBuilder) { + assertThatExceptionOfType(SnippetException.class) + .isThrownBy(() -> new RequestFieldsSnippet(Collections.emptyList()) + .document(operationBuilder.request("http://localhost") + .content("5") + .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("5") + .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("5") + .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("") + .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("5") + .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.emptyList()) + .document(operationBuilder.request("http://localhost") + .content("Some plain text") + .header(HttpHeaders.CONTENT_TYPE, MediaType.TEXT_PLAIN_VALUE) + .build())) + .withMessage("Cannot handle content with type text/plain 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]"); } } diff --git a/spring-restdocs-core/src/test/java/org/springframework/restdocs/payload/RequestPartFieldsSnippetFailureTests.java b/spring-restdocs-core/src/test/java/org/springframework/restdocs/payload/RequestPartFieldsSnippetFailureTests.java deleted file mode 100644 index 84d6c579..00000000 --- a/spring-restdocs-core/src/test/java/org/springframework/restdocs/payload/RequestPartFieldsSnippetFailureTests.java +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright 2014-present 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.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"); - } - -} diff --git a/spring-restdocs-core/src/test/java/org/springframework/restdocs/payload/RequestPartFieldsSnippetTests.java b/spring-restdocs-core/src/test/java/org/springframework/restdocs/payload/RequestPartFieldsSnippetTests.java index 9504382e..5829e46e 100644 --- a/spring-restdocs-core/src/test/java/org/springframework/restdocs/payload/RequestPartFieldsSnippetTests.java +++ b/spring-restdocs-core/src/test/java/org/springframework/restdocs/payload/RequestPartFieldsSnippetTests.java @@ -20,13 +20,15 @@ import java.util.Arrays; import java.util.Collections; -import org.junit.Test; - -import org.springframework.restdocs.AbstractSnippetTests; import org.springframework.restdocs.operation.Operation; -import org.springframework.restdocs.templates.TemplateFormat; +import org.springframework.restdocs.snippet.SnippetException; +import org.springframework.restdocs.testfixtures.jupiter.AssertableSnippets; +import org.springframework.restdocs.testfixtures.jupiter.OperationBuilder; +import org.springframework.restdocs.testfixtures.jupiter.RenderedSnippetTest; +import org.springframework.restdocs.testfixtures.jupiter.SnippetTest; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.springframework.restdocs.payload.PayloadDocumentation.beneathPath; import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath; @@ -36,86 +38,110 @@ * @author Mathieu Pousse * @author Andy Wilkinson */ -public class RequestPartFieldsSnippetTests extends AbstractSnippetTests { - - public RequestPartFieldsSnippetTests(String name, TemplateFormat templateFormat) { - super(name, templateFormat); - } +public class RequestPartFieldsSnippetTests { - @Test - public void mapRequestPartFields() throws IOException { + @RenderedSnippetTest + void mapRequestPartFields(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException { new RequestPartFieldsSnippet("one", Arrays.asList(fieldWithPath("a.b").description("one"), fieldWithPath("a.c").description("two"), fieldWithPath("a").description("three"))) - .document(this.operationBuilder.request("http://localhost") + .document(operationBuilder.request("http://localhost") .part("one", "{\"a\": {\"b\": 5, \"c\": \"charlie\"}}".getBytes()) .build()); - assertThat(this.generatedSnippets.requestPartFields("one")) - .is(tableWithHeader("Path", "Type", "Description").row("`a.b`", "`Number`", "one") - .row("`a.c`", "`String`", "two") - .row("`a`", "`Object`", "three")); + assertThat(snippets.requestPartFields("one")).isTable((table) -> table.withHeader("Path", "Type", "Description") + .row("`a.b`", "`Number`", "one") + .row("`a.c`", "`String`", "two") + .row("`a`", "`Object`", "three")); } - @Test - public void mapRequestPartSubsectionFields() throws IOException { + @RenderedSnippetTest + void mapRequestPartSubsectionFields(OperationBuilder operationBuilder, AssertableSnippets snippets) + throws IOException { new RequestPartFieldsSnippet("one", beneathPath("a"), Arrays.asList(fieldWithPath("b").description("one"), fieldWithPath("c").description("two"))) - .document(this.operationBuilder.request("http://localhost") + .document(operationBuilder.request("http://localhost") .part("one", "{\"a\": {\"b\": 5, \"c\": \"charlie\"}}".getBytes()) .build()); - assertThat(this.generatedSnippets.snippet("request-part-one-fields-beneath-a")) - .is(tableWithHeader("Path", "Type", "Description").row("`b`", "`Number`", "one") + assertThat(snippets.requestPartFields("one", "beneath-a")) + .isTable((table) -> table.withHeader("Path", "Type", "Description") + .row("`b`", "`Number`", "one") .row("`c`", "`String`", "two")); } - @Test - public void multipleRequestParts() throws IOException { - Operation operation = this.operationBuilder.request("http://localhost") + @RenderedSnippetTest + void multipleRequestParts(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException { + Operation operation = operationBuilder.request("http://localhost") .part("one", "{}".getBytes()) .and() .part("two", "{}".getBytes()) .build(); new RequestPartFieldsSnippet("one", Collections.emptyList()).document(operation); new RequestPartFieldsSnippet("two", Collections.emptyList()).document(operation); - assertThat(this.generatedSnippets.requestPartFields("one")).isNotNull(); - assertThat(this.generatedSnippets.requestPartFields("two")).isNotNull(); + assertThat(snippets.requestPartFields("one")).isNotNull(); + assertThat(snippets.requestPartFields("two")).isNotNull(); } - @Test - public void allUndocumentedRequestPartFieldsCanBeIgnored() throws IOException { - new RequestPartFieldsSnippet("one", Arrays.asList(fieldWithPath("b").description("Field b")), true) - .document(this.operationBuilder.request("http://localhost") - .part("one", "{\"a\": 5, \"b\": 4}".getBytes()) - .build()); - assertThat(this.generatedSnippets.requestPartFields("one")) - .is(tableWithHeader("Path", "Type", "Description").row("`b`", "`Number`", "Field b")); + @RenderedSnippetTest + void allUndocumentedRequestPartFieldsCanBeIgnored(OperationBuilder operationBuilder, AssertableSnippets snippets) + throws IOException { + new RequestPartFieldsSnippet("one", Arrays.asList(fieldWithPath("b").description("Field b")), true).document( + operationBuilder.request("http://localhost").part("one", "{\"a\": 5, \"b\": 4}".getBytes()).build()); + assertThat(snippets.requestPartFields("one")) + .isTable((table) -> table.withHeader("Path", "Type", "Description").row("`b`", "`Number`", "Field b")); } - @Test - public void additionalDescriptors() throws IOException { + @RenderedSnippetTest + void additionalDescriptors(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException { PayloadDocumentation .requestPartFields("one", fieldWithPath("a.b").description("one"), fieldWithPath("a.c").description("two")) .and(fieldWithPath("a").description("three")) - .document(this.operationBuilder.request("http://localhost") + .document(operationBuilder.request("http://localhost") .part("one", "{\"a\": {\"b\": 5, \"c\": \"charlie\"}}".getBytes()) .build()); - assertThat(this.generatedSnippets.requestPartFields("one")) - .is(tableWithHeader("Path", "Type", "Description").row("`a.b`", "`Number`", "one") - .row("`a.c`", "`String`", "two") - .row("`a`", "`Object`", "three")); + assertThat(snippets.requestPartFields("one")).isTable((table) -> table.withHeader("Path", "Type", "Description") + .row("`a.b`", "`Number`", "one") + .row("`a.c`", "`String`", "two") + .row("`a`", "`Object`", "three")); } - @Test - public void prefixedAdditionalDescriptors() throws IOException { + @RenderedSnippetTest + void prefixedAdditionalDescriptors(OperationBuilder operationBuilder, AssertableSnippets snippets) + throws IOException { PayloadDocumentation.requestPartFields("one", fieldWithPath("a").description("one")) .andWithPrefix("a.", fieldWithPath("b").description("two"), fieldWithPath("c").description("three")) - .document(this.operationBuilder.request("http://localhost") + .document(operationBuilder.request("http://localhost") .part("one", "{\"a\": {\"b\": 5, \"c\": \"charlie\"}}".getBytes()) .build()); - assertThat(this.generatedSnippets.requestPartFields("one")) - .is(tableWithHeader("Path", "Type", "Description").row("`a`", "`Object`", "one") - .row("`a.b`", "`Number`", "two") - .row("`a.c`", "`String`", "three")); + assertThat(snippets.requestPartFields("one")).isTable((table) -> table.withHeader("Path", "Type", "Description") + .row("`a`", "`Object`", "one") + .row("`a.b`", "`Number`", "two") + .row("`a.c`", "`String`", "three")); + } + + @SnippetTest + void undocumentedRequestPartField(OperationBuilder operationBuilder) { + assertThatExceptionOfType(SnippetException.class) + .isThrownBy(() -> new RequestPartFieldsSnippet("part", Collections.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"); } } diff --git a/spring-restdocs-core/src/test/java/org/springframework/restdocs/payload/ResponseBodySnippetTests.java b/spring-restdocs-core/src/test/java/org/springframework/restdocs/payload/ResponseBodySnippetTests.java index 0c1117a7..77e89c66 100644 --- a/spring-restdocs-core/src/test/java/org/springframework/restdocs/payload/ResponseBodySnippetTests.java +++ b/spring-restdocs-core/src/test/java/org/springframework/restdocs/payload/ResponseBodySnippetTests.java @@ -18,19 +18,14 @@ import java.io.IOException; -import org.junit.Test; - import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; -import org.springframework.restdocs.AbstractSnippetTests; -import org.springframework.restdocs.templates.TemplateEngine; -import org.springframework.restdocs.templates.TemplateFormat; -import org.springframework.restdocs.templates.TemplateResourceResolver; -import org.springframework.restdocs.templates.mustache.MustacheTemplateEngine; +import org.springframework.restdocs.testfixtures.jupiter.AssertableSnippets; +import org.springframework.restdocs.testfixtures.jupiter.OperationBuilder; +import org.springframework.restdocs.testfixtures.jupiter.RenderedSnippetTest; +import org.springframework.restdocs.testfixtures.jupiter.SnippetTemplate; import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.BDDMockito.given; -import static org.mockito.Mockito.mock; import static org.springframework.restdocs.payload.PayloadDocumentation.beneathPath; import static org.springframework.restdocs.payload.PayloadDocumentation.responseBody; import static org.springframework.restdocs.snippet.Attributes.attributes; @@ -41,77 +36,72 @@ * * @author Andy Wilkinson */ -public class ResponseBodySnippetTests extends AbstractSnippetTests { +class ResponseBodySnippetTests { - public ResponseBodySnippetTests(String name, TemplateFormat templateFormat) { - super(name, templateFormat); + @RenderedSnippetTest + void responseWithBody(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException { + new ResponseBodySnippet().document(operationBuilder.response().content("some content").build()); + assertThat(snippets.responseBody()) + .isCodeBlock((codeBlock) -> codeBlock.withOptions("nowrap").content("some content")); } - @Test - public void responseWithBody() throws IOException { - new ResponseBodySnippet().document(this.operationBuilder.response().content("some content").build()); - assertThat(this.generatedSnippets.snippet("response-body")) - .is(codeBlock(null, "nowrap").withContent("some content")); + @RenderedSnippetTest + void responseWithNoBody(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException { + new ResponseBodySnippet().document(operationBuilder.response().build()); + assertThat(snippets.responseBody()).isCodeBlock((codeBlock) -> codeBlock.withOptions("nowrap").content("")); } - @Test - public void responseWithNoBody() throws IOException { - new ResponseBodySnippet().document(this.operationBuilder.response().build()); - assertThat(this.generatedSnippets.snippet("response-body")).is(codeBlock(null, "nowrap").withContent("")); + @RenderedSnippetTest + void responseWithJsonMediaType(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException { + new ResponseBodySnippet().document( + operationBuilder.response().header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE).build()); + assertThat(snippets.responseBody()) + .isCodeBlock((codeBlock) -> codeBlock.withLanguageAndOptions("json", "nowrap").content("")); } - @Test - public void responseWithJsonMediaType() throws IOException { - new ResponseBodySnippet().document(this.operationBuilder.response() - .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) - .build()); - assertThat(this.generatedSnippets.snippet("response-body")).is(codeBlock("json", "nowrap").withContent("")); - } - - @Test - public void responseWithJsonSubtypeMediaType() throws IOException { - new ResponseBodySnippet().document(this.operationBuilder.response() + @RenderedSnippetTest + void responseWithJsonSubtypeMediaType(OperationBuilder operationBuilder, AssertableSnippets snippets) + throws IOException { + new ResponseBodySnippet().document(operationBuilder.response() .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_PROBLEM_JSON_VALUE) .build()); - assertThat(this.generatedSnippets.snippet("response-body")).is(codeBlock("json", "nowrap").withContent("")); + assertThat(snippets.responseBody()) + .isCodeBlock((codeBlock) -> codeBlock.withLanguageAndOptions("json", "nowrap").content("")); } - @Test - public void responseWithXmlMediaType() throws IOException { - new ResponseBodySnippet().document(this.operationBuilder.response() - .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_XML_VALUE) - .build()); - assertThat(this.generatedSnippets.snippet("response-body")).is(codeBlock("xml", "nowrap").withContent("")); + @RenderedSnippetTest + void responseWithXmlMediaType(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException { + new ResponseBodySnippet().document( + operationBuilder.response().header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_XML_VALUE).build()); + assertThat(snippets.responseBody()) + .isCodeBlock((codeBlock) -> codeBlock.withLanguageAndOptions("xml", "nowrap").content("")); } - @Test - public void responseWithXmlSubtypeMediaType() throws IOException { - new ResponseBodySnippet().document(this.operationBuilder.response() + @RenderedSnippetTest + void responseWithXmlSubtypeMediaType(OperationBuilder operationBuilder, AssertableSnippets snippets) + throws IOException { + new ResponseBodySnippet().document(operationBuilder.response() .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_ATOM_XML_VALUE) .build()); - assertThat(this.generatedSnippets.snippet("response-body")).is(codeBlock("xml", "nowrap").withContent("")); + assertThat(snippets.responseBody()) + .isCodeBlock((codeBlock) -> codeBlock.withLanguageAndOptions("xml", "nowrap").content("")); } - @Test - public void subsectionOfResponseBody() throws IOException { + @RenderedSnippetTest + void subsectionOfResponseBody(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException { responseBody(beneathPath("a.b")) - .document(this.operationBuilder.response().content("{\"a\":{\"b\":{\"c\":5}}}").build()); - assertThat(this.generatedSnippets.snippet("response-body-beneath-a.b")) - .is(codeBlock(null, "nowrap").withContent("{\"c\":5}")); + .document(operationBuilder.response().content("{\"a\":{\"b\":{\"c\":5}}}").build()); + assertThat(snippets.responseBody("beneath-a.b")) + .isCodeBlock((codeBlock) -> codeBlock.withOptions("nowrap").content("{\"c\":5}")); } - @Test - public void customSnippetAttributes() throws IOException { - TemplateResourceResolver resolver = mock(TemplateResourceResolver.class); - given(resolver.resolveTemplateResource("response-body")) - .willReturn(snippetResource("response-body-with-language")); - new ResponseBodySnippet(attributes(key("language").value("json"))).document( - this.operationBuilder.attribute(TemplateEngine.class.getName(), new MustacheTemplateEngine(resolver)) - .response() - .content("{\"a\":\"alpha\"}") - .build()); - assertThat(this.generatedSnippets.snippet("response-body")) - .is(codeBlock("json", "nowrap").withContent("{\"a\":\"alpha\"}")); + @RenderedSnippetTest + @SnippetTemplate(snippet = "response-body", template = "response-body-with-language") + void customSnippetAttributes(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException { + new ResponseBodySnippet(attributes(key("language").value("json"))) + .document(operationBuilder.response().content("{\"a\":\"alpha\"}").build()); + assertThat(snippets.responseBody()).isCodeBlock( + (codeBlock) -> codeBlock.withLanguageAndOptions("json", "nowrap").content("{\"a\":\"alpha\"}")); } } diff --git a/spring-restdocs-core/src/test/java/org/springframework/restdocs/payload/ResponseFieldsSnippetFailureTests.java b/spring-restdocs-core/src/test/java/org/springframework/restdocs/payload/ResponseFieldsSnippetFailureTests.java deleted file mode 100644 index 70edea7f..00000000 --- a/spring-restdocs-core/src/test/java/org/springframework/restdocs/payload/ResponseFieldsSnippetFailureTests.java +++ /dev/null @@ -1,175 +0,0 @@ -/* - * Copyright 2014-present 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.emptyList()) - .document(this.operationBuilder.response() - .content("5") - .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("foo") - .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("bar") - .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_XML_VALUE) - .build())) - .withMessage(String.format("The following parts of the payload were not documented:%nbar%n")); - } - - @Test - public void xmlResponseFieldWithNoType() { - assertThatExceptionOfType(FieldTypeRequiredException.class) - .isThrownBy(() -> new ResponseFieldsSnippet(Arrays.asList(fieldWithPath("a").description("one"))) - .document(this.operationBuilder.response() - .content("5") - .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("") - .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("5") - .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.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]"); - } - -} diff --git a/spring-restdocs-core/src/test/java/org/springframework/restdocs/payload/ResponseFieldsSnippetTests.java b/spring-restdocs-core/src/test/java/org/springframework/restdocs/payload/ResponseFieldsSnippetTests.java index 1a0a3db3..555897f2 100644 --- a/spring-restdocs-core/src/test/java/org/springframework/restdocs/payload/ResponseFieldsSnippetTests.java +++ b/spring-restdocs-core/src/test/java/org/springframework/restdocs/payload/ResponseFieldsSnippetTests.java @@ -18,21 +18,19 @@ import java.io.IOException; import java.util.Arrays; - -import org.junit.Test; +import java.util.Collections; import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; -import org.springframework.restdocs.AbstractSnippetTests; -import org.springframework.restdocs.templates.TemplateEngine; -import org.springframework.restdocs.templates.TemplateFormat; -import org.springframework.restdocs.templates.TemplateFormats; -import org.springframework.restdocs.templates.TemplateResourceResolver; -import org.springframework.restdocs.templates.mustache.MustacheTemplateEngine; +import org.springframework.restdocs.snippet.SnippetException; +import org.springframework.restdocs.testfixtures.jupiter.AssertableSnippets; +import org.springframework.restdocs.testfixtures.jupiter.OperationBuilder; +import org.springframework.restdocs.testfixtures.jupiter.RenderedSnippetTest; +import org.springframework.restdocs.testfixtures.jupiter.SnippetTemplate; +import org.springframework.restdocs.testfixtures.jupiter.SnippetTest; import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.BDDMockito.given; -import static org.mockito.Mockito.mock; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.springframework.restdocs.payload.PayloadDocumentation.beneathPath; import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath; import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields; @@ -45,355 +43,486 @@ * @author Andy Wilkinson * @author Sungjun Lee */ -public class ResponseFieldsSnippetTests extends AbstractSnippetTests { - - public ResponseFieldsSnippetTests(String name, TemplateFormat templateFormat) { - super(name, templateFormat); - } +public class ResponseFieldsSnippetTests { - @Test - public void mapResponseWithFields() throws IOException { + @RenderedSnippetTest + void mapResponseWithFields(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException { new ResponseFieldsSnippet(Arrays.asList(fieldWithPath("id").description("one"), fieldWithPath("date").description("two"), fieldWithPath("assets").description("three"), fieldWithPath("assets[]").description("four"), fieldWithPath("assets[].id").description("five"), fieldWithPath("assets[].name").description("six"))) - .document(this.operationBuilder.response() + .document(operationBuilder.response() .content("{\"id\": 67,\"date\": \"2015-01-20\",\"assets\":" + " [{\"id\":356,\"name\": \"sample\"}]}") .build()); - assertThat(this.generatedSnippets.responseFields()) - .is(tableWithHeader("Path", "Type", "Description").row("`id`", "`Number`", "one") - .row("`date`", "`String`", "two") - .row("`assets`", "`Array`", "three") - .row("`assets[]`", "`Array`", "four") - .row("`assets[].id`", "`Number`", "five") - .row("`assets[].name`", "`String`", "six")); + assertThat(snippets.responseFields()).isTable((table) -> table.withHeader("Path", "Type", "Description") + .row("`id`", "`Number`", "one") + .row("`date`", "`String`", "two") + .row("`assets`", "`Array`", "three") + .row("`assets[]`", "`Array`", "four") + .row("`assets[].id`", "`Number`", "five") + .row("`assets[].name`", "`String`", "six")); } - @Test - public void mapResponseWithNullField() throws IOException { + @RenderedSnippetTest + void mapResponseWithNullField(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException { new ResponseFieldsSnippet(Arrays.asList(fieldWithPath("a.b").description("one"))) - .document(this.operationBuilder.response().content("{\"a\": {\"b\": null}}").build()); - assertThat(this.generatedSnippets.responseFields()) - .is(tableWithHeader("Path", "Type", "Description").row("`a.b`", "`Null`", "one")); + .document(operationBuilder.response().content("{\"a\": {\"b\": null}}").build()); + assertThat(snippets.responseFields()) + .isTable((table) -> table.withHeader("Path", "Type", "Description").row("`a.b`", "`Null`", "one")); } - @Test - public void subsectionOfMapResponse() throws IOException { + @RenderedSnippetTest + void subsectionOfMapResponse(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException { responseFields(beneathPath("a"), fieldWithPath("b").description("one"), fieldWithPath("c").description("two")) - .document(this.operationBuilder.response().content("{\"a\": {\"b\": 5, \"c\": \"charlie\"}}").build()); - assertThat(this.generatedSnippets.snippet("response-fields-beneath-a")) - .is(tableWithHeader("Path", "Type", "Description").row("`b`", "`Number`", "one") + .document(operationBuilder.response().content("{\"a\": {\"b\": 5, \"c\": \"charlie\"}}").build()); + assertThat(snippets.responseFields("beneath-a")) + .isTable((table) -> table.withHeader("Path", "Type", "Description") + .row("`b`", "`Number`", "one") .row("`c`", "`String`", "two")); } - @Test - public void subsectionOfMapResponseBeneathAnArray() throws IOException { + @RenderedSnippetTest + void subsectionOfMapResponseBeneathAnArray(OperationBuilder operationBuilder, AssertableSnippets snippets) + throws IOException { responseFields(beneathPath("a.b.[]"), fieldWithPath("c").description("one"), fieldWithPath("d.[].e").description("two")) - .document(this.operationBuilder.response() + .document(operationBuilder.response() .content("{\"a\": {\"b\": [{\"c\": 1, \"d\": [{\"e\": 5}]}, {\"c\": 3, \"d\": [{\"e\": 4}]}]}}") .build()); - assertThat(this.generatedSnippets.snippet("response-fields-beneath-a.b.[]")) - .is(tableWithHeader("Path", "Type", "Description").row("`c`", "`Number`", "one") + assertThat(snippets.responseFields("beneath-a.b.[]")) + .isTable((table) -> table.withHeader("Path", "Type", "Description") + .row("`c`", "`Number`", "one") .row("`d.[].e`", "`Number`", "two")); } - @Test - public void subsectionOfMapResponseWithCommonsPrefix() throws IOException { + @RenderedSnippetTest + void subsectionOfMapResponseWithCommonsPrefix(OperationBuilder operationBuilder, AssertableSnippets snippets) + throws IOException { responseFields(beneathPath("a")).andWithPrefix("b.", fieldWithPath("c").description("two")) - .document(this.operationBuilder.response().content("{\"a\": {\"b\": {\"c\": \"charlie\"}}}").build()); - assertThat(this.generatedSnippets.snippet("response-fields-beneath-a")) - .is(tableWithHeader("Path", "Type", "Description").row("`b.c`", "`String`", "two")); + .document(operationBuilder.response().content("{\"a\": {\"b\": {\"c\": \"charlie\"}}}").build()); + assertThat(snippets.responseFields("beneath-a")) + .isTable((table) -> table.withHeader("Path", "Type", "Description").row("`b.c`", "`String`", "two")); } - @Test - public void arrayResponseWithFields() throws IOException { + @RenderedSnippetTest + void arrayResponseWithFields(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException { new ResponseFieldsSnippet(Arrays.asList(fieldWithPath("[]a.b").description("one"), fieldWithPath("[]a.c").description("two"), fieldWithPath("[]a").description("three"))) - .document(this.operationBuilder.response() + .document(operationBuilder.response() .content("[{\"a\": {\"b\": 5, \"c\":\"charlie\"}}," + "{\"a\": {\"b\": 4, \"c\":\"chalk\"}}]") .build()); - assertThat(this.generatedSnippets.responseFields()) - .is(tableWithHeader("Path", "Type", "Description").row("`[]a.b`", "`Number`", "one") - .row("`[]a.c`", "`String`", "two") - .row("`[]a`", "`Object`", "three")); + assertThat(snippets.responseFields()).isTable((table) -> table.withHeader("Path", "Type", "Description") + .row("`[]a.b`", "`Number`", "one") + .row("`[]a.c`", "`String`", "two") + .row("`[]a`", "`Object`", "three")); } - @Test - public void arrayResponseWithAlwaysNullField() throws IOException { - new ResponseFieldsSnippet(Arrays.asList(fieldWithPath("[]a.b").description("one"))) - .document(this.operationBuilder.response() - .content("[{\"a\": {\"b\": null}}," + "{\"a\": {\"b\": null}}]") - .build()); - assertThat(this.generatedSnippets.responseFields()) - .is(tableWithHeader("Path", "Type", "Description").row("`[]a.b`", "`Null`", "one")); + @RenderedSnippetTest + void arrayResponseWithAlwaysNullField(OperationBuilder operationBuilder, AssertableSnippets snippets) + throws IOException { + new ResponseFieldsSnippet(Arrays.asList(fieldWithPath("[]a.b").description("one"))).document( + operationBuilder.response().content("[{\"a\": {\"b\": null}}," + "{\"a\": {\"b\": null}}]").build()); + assertThat(snippets.responseFields()) + .isTable((table) -> table.withHeader("Path", "Type", "Description").row("`[]a.b`", "`Null`", "one")); } - @Test - public void arrayResponse() throws IOException { + @RenderedSnippetTest + void arrayResponse(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException { new ResponseFieldsSnippet(Arrays.asList(fieldWithPath("[]").description("one"))) - .document(this.operationBuilder.response().content("[\"a\", \"b\", \"c\"]").build()); - assertThat(this.generatedSnippets.responseFields()) - .is(tableWithHeader("Path", "Type", "Description").row("`[]`", "`Array`", "one")); + .document(operationBuilder.response().content("[\"a\", \"b\", \"c\"]").build()); + assertThat(snippets.responseFields()) + .isTable((table) -> table.withHeader("Path", "Type", "Description").row("`[]`", "`Array`", "one")); } - @Test - public void ignoredResponseField() throws IOException { + @RenderedSnippetTest + void ignoredResponseField(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException { new ResponseFieldsSnippet( Arrays.asList(fieldWithPath("a").ignored(), fieldWithPath("b").description("Field b"))) - .document(this.operationBuilder.response().content("{\"a\": 5, \"b\": 4}").build()); - assertThat(this.generatedSnippets.responseFields()) - .is(tableWithHeader("Path", "Type", "Description").row("`b`", "`Number`", "Field b")); + .document(operationBuilder.response().content("{\"a\": 5, \"b\": 4}").build()); + assertThat(snippets.responseFields()) + .isTable((table) -> table.withHeader("Path", "Type", "Description").row("`b`", "`Number`", "Field b")); } - @Test - public void allUndocumentedFieldsCanBeIgnored() throws IOException { + @RenderedSnippetTest + void allUndocumentedFieldsCanBeIgnored(OperationBuilder operationBuilder, AssertableSnippets snippets) + throws IOException { new ResponseFieldsSnippet(Arrays.asList(fieldWithPath("b").description("Field b")), true) - .document(this.operationBuilder.response().content("{\"a\": 5, \"b\": 4}").build()); - assertThat(this.generatedSnippets.responseFields()) - .is(tableWithHeader("Path", "Type", "Description").row("`b`", "`Number`", "Field b")); + .document(operationBuilder.response().content("{\"a\": 5, \"b\": 4}").build()); + assertThat(snippets.responseFields()) + .isTable((table) -> table.withHeader("Path", "Type", "Description").row("`b`", "`Number`", "Field b")); } - @Test - public void allUndocumentedFieldsContinueToBeIgnoredAfterAddingDescriptors() throws IOException { + @RenderedSnippetTest + void allUndocumentedFieldsContinueToBeIgnoredAfterAddingDescriptors(OperationBuilder operationBuilder, + AssertableSnippets snippets) throws IOException { new ResponseFieldsSnippet(Arrays.asList(fieldWithPath("b").description("Field b")), true) .andWithPrefix("c.", fieldWithPath("d").description("Field d")) - .document(this.operationBuilder.response().content("{\"a\":5,\"b\":4,\"c\":{\"d\": 3}}").build()); - assertThat(this.generatedSnippets.responseFields()) - .is(tableWithHeader("Path", "Type", "Description").row("`b`", "`Number`", "Field b") - .row("`c.d`", "`Number`", "Field d")); + .document(operationBuilder.response().content("{\"a\":5,\"b\":4,\"c\":{\"d\": 3}}").build()); + assertThat(snippets.responseFields()).isTable((table) -> table.withHeader("Path", "Type", "Description") + .row("`b`", "`Number`", "Field b") + .row("`c.d`", "`Number`", "Field d")); } - @Test - public void responseFieldsWithCustomAttributes() throws IOException { - TemplateResourceResolver resolver = mock(TemplateResourceResolver.class); - given(resolver.resolveTemplateResource("response-fields")) - .willReturn(snippetResource("response-fields-with-title")); + @RenderedSnippetTest + @SnippetTemplate(snippet = "response-fields", template = "response-fields-with-title") + void responseFieldsWithCustomAttributes(OperationBuilder operationBuilder, AssertableSnippets snippets) + throws IOException { new ResponseFieldsSnippet(Arrays.asList(fieldWithPath("a").description("one")), attributes(key("title").value("Custom title"))) - .document(this.operationBuilder - .attribute(TemplateEngine.class.getName(), new MustacheTemplateEngine(resolver)) - .response() - .content("{\"a\": \"foo\"}") - .build()); - assertThat(this.generatedSnippets.responseFields()).contains("Custom title"); + .document(operationBuilder.response().content("{\"a\": \"foo\"}").build()); + assertThat(snippets.responseFields()).contains("Custom title"); } - @Test - public void missingOptionalResponseField() throws IOException { + @RenderedSnippetTest + void missingOptionalResponseField(OperationBuilder operationBuilder, AssertableSnippets snippets) + throws IOException { new ResponseFieldsSnippet( Arrays.asList(fieldWithPath("a.b").description("one").type(JsonFieldType.STRING).optional())) - .document(this.operationBuilder.response().content("{}").build()); - assertThat(this.generatedSnippets.responseFields()) - .is(tableWithHeader("Path", "Type", "Description").row("`a.b`", "`String`", "one")); + .document(operationBuilder.response().content("{}").build()); + assertThat(snippets.responseFields()) + .isTable((table) -> table.withHeader("Path", "Type", "Description").row("`a.b`", "`String`", "one")); } - @Test - public void missingIgnoredOptionalResponseFieldDoesNotRequireAType() throws IOException { + @RenderedSnippetTest + void missingIgnoredOptionalResponseFieldDoesNotRequireAType(OperationBuilder operationBuilder, + AssertableSnippets snippets) throws IOException { new ResponseFieldsSnippet(Arrays.asList(fieldWithPath("a.b").description("one").ignored().optional())) - .document(this.operationBuilder.response().content("{}").build()); - assertThat(this.generatedSnippets.responseFields()).is(tableWithHeader("Path", "Type", "Description")); + .document(operationBuilder.response().content("{}").build()); + assertThat(snippets.responseFields()).isTable((table) -> table.withHeader("Path", "Type", "Description")); } - @Test - public void presentOptionalResponseField() throws IOException { + @RenderedSnippetTest + void presentOptionalResponseField(OperationBuilder operationBuilder, AssertableSnippets snippets) + throws IOException { new ResponseFieldsSnippet( Arrays.asList(fieldWithPath("a.b").description("one").type(JsonFieldType.STRING).optional())) - .document(this.operationBuilder.response().content("{\"a\": { \"b\": \"bravo\"}}").build()); - assertThat(this.generatedSnippets.responseFields()) - .is(tableWithHeader("Path", "Type", "Description").row("`a.b`", "`String`", "one")); + .document(operationBuilder.response().content("{\"a\": { \"b\": \"bravo\"}}").build()); + assertThat(snippets.responseFields()) + .isTable((table) -> table.withHeader("Path", "Type", "Description").row("`a.b`", "`String`", "one")); } - @Test - public void responseFieldsWithCustomDescriptorAttributes() throws IOException { - TemplateResourceResolver resolver = mock(TemplateResourceResolver.class); - given(resolver.resolveTemplateResource("response-fields")) - .willReturn(snippetResource("response-fields-with-extra-column")); + @RenderedSnippetTest + @SnippetTemplate(snippet = "response-fields", template = "response-fields-with-extra-column") + void responseFieldsWithCustomDescriptorAttributes(OperationBuilder operationBuilder, AssertableSnippets snippets) + throws IOException { new ResponseFieldsSnippet( Arrays.asList(fieldWithPath("a.b").description("one").attributes(key("foo").value("alpha")), fieldWithPath("a.c").description("two").attributes(key("foo").value("bravo")), fieldWithPath("a").description("three").attributes(key("foo").value("charlie")))) - .document(this.operationBuilder - .attribute(TemplateEngine.class.getName(), new MustacheTemplateEngine(resolver)) - .response() - .content("{\"a\": {\"b\": 5, \"c\": \"charlie\"}}") - .build()); - assertThat(this.generatedSnippets.responseFields()) - .is(tableWithHeader("Path", "Type", "Description", "Foo").row("a.b", "Number", "one", "alpha") - .row("a.c", "String", "two", "bravo") - .row("a", "Object", "three", "charlie")); + .document(operationBuilder.response().content("{\"a\": {\"b\": 5, \"c\": \"charlie\"}}").build()); + assertThat(snippets.responseFields()).isTable((table) -> table.withHeader("Path", "Type", "Description", "Foo") + .row("a.b", "Number", "one", "alpha") + .row("a.c", "String", "two", "bravo") + .row("a", "Object", "three", "charlie")); } - @Test - public void fieldWithExplicitExactlyMatchingType() throws IOException { + @RenderedSnippetTest + void fieldWithExplicitExactlyMatchingType(OperationBuilder operationBuilder, AssertableSnippets snippets) + throws IOException { new ResponseFieldsSnippet(Arrays.asList(fieldWithPath("a").description("one").type(JsonFieldType.NUMBER))) - .document(this.operationBuilder.response().content("{\"a\": 5 }").build()); - assertThat(this.generatedSnippets.responseFields()) - .is(tableWithHeader("Path", "Type", "Description").row("`a`", "`Number`", "one")); + .document(operationBuilder.response().content("{\"a\": 5 }").build()); + assertThat(snippets.responseFields()) + .isTable((table) -> table.withHeader("Path", "Type", "Description").row("`a`", "`Number`", "one")); } - @Test - public void fieldWithExplicitVariesType() throws IOException { + @RenderedSnippetTest + void fieldWithExplicitVariesType(OperationBuilder operationBuilder, AssertableSnippets snippets) + throws IOException { new ResponseFieldsSnippet(Arrays.asList(fieldWithPath("a").description("one").type(JsonFieldType.VARIES))) - .document(this.operationBuilder.response().content("{\"a\": 5 }").build()); - assertThat(this.generatedSnippets.responseFields()) - .is(tableWithHeader("Path", "Type", "Description").row("`a`", "`Varies`", "one")); + .document(operationBuilder.response().content("{\"a\": 5 }").build()); + assertThat(snippets.responseFields()) + .isTable((table) -> table.withHeader("Path", "Type", "Description").row("`a`", "`Varies`", "one")); } - @Test - public void applicationXmlResponseFields() throws IOException { - xmlResponseFields(MediaType.APPLICATION_XML); + @RenderedSnippetTest + void applicationXmlResponseFields(OperationBuilder operationBuilder, AssertableSnippets snippets) + throws IOException { + xmlResponseFields(MediaType.APPLICATION_XML, operationBuilder, snippets); } - @Test - public void textXmlResponseFields() throws IOException { - xmlResponseFields(MediaType.TEXT_XML); + @RenderedSnippetTest + void textXmlResponseFields(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException { + xmlResponseFields(MediaType.TEXT_XML, operationBuilder, snippets); } - @Test - public void customXmlResponseFields() throws IOException { - xmlResponseFields(MediaType.parseMediaType("application/vnd.com.example+xml")); + @RenderedSnippetTest + void customXmlResponseFields(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException { + xmlResponseFields(MediaType.parseMediaType("application/vnd.com.example+xml"), operationBuilder, snippets); } - private void xmlResponseFields(MediaType contentType) throws IOException { + private void xmlResponseFields(MediaType contentType, OperationBuilder operationBuilder, + AssertableSnippets snippets) throws IOException { new ResponseFieldsSnippet(Arrays.asList(fieldWithPath("a/b").description("one").type("b"), fieldWithPath("a/c").description("two").type("c"), fieldWithPath("a").description("three").type("a"))) - .document(this.operationBuilder.response() + .document(operationBuilder.response() .content("5charlie") .header(HttpHeaders.CONTENT_TYPE, contentType.toString()) .build()); - assertThat(this.generatedSnippets.responseFields()) - .is(tableWithHeader("Path", "Type", "Description").row("`a/b`", "`b`", "one") - .row("`a/c`", "`c`", "two") - .row("`a`", "`a`", "three")); + assertThat(snippets.responseFields()).isTable((table) -> table.withHeader("Path", "Type", "Description") + .row("`a/b`", "`b`", "one") + .row("`a/c`", "`c`", "two") + .row("`a`", "`a`", "three")); } - @Test - public void xmlAttribute() throws IOException { + @RenderedSnippetTest + void xmlAttribute(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException { new ResponseFieldsSnippet(Arrays.asList(fieldWithPath("a").description("one").type("b"), fieldWithPath("a/@id").description("two").type("c"))) - .document(this.operationBuilder.response() + .document(operationBuilder.response() .content("foo") .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_XML_VALUE) .build()); - assertThat(this.generatedSnippets.responseFields()) - .is(tableWithHeader("Path", "Type", "Description").row("`a`", "`b`", "one").row("`a/@id`", "`c`", "two")); + assertThat(snippets.responseFields()).isTable((table) -> table.withHeader("Path", "Type", "Description") + .row("`a`", "`b`", "one") + .row("`a/@id`", "`c`", "two")); } - @Test - public void missingOptionalXmlAttribute() throws IOException { + @RenderedSnippetTest + void missingOptionalXmlAttribute(OperationBuilder operationBuilder, AssertableSnippets snippets) + throws IOException { new ResponseFieldsSnippet(Arrays.asList(fieldWithPath("a").description("one").type("b"), fieldWithPath("a/@id").description("two").type("c").optional())) - .document(this.operationBuilder.response() + .document(operationBuilder.response() .content("foo") .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_XML_VALUE) .build()); - assertThat(this.generatedSnippets.responseFields()) - .is(tableWithHeader("Path", "Type", "Description").row("`a`", "`b`", "one").row("`a/@id`", "`c`", "two")); + assertThat(snippets.responseFields()).isTable((table) -> table.withHeader("Path", "Type", "Description") + .row("`a`", "`b`", "one") + .row("`a/@id`", "`c`", "two")); } - @Test - public void undocumentedAttributeDoesNotCauseFailure() throws IOException { + @RenderedSnippetTest + void undocumentedAttributeDoesNotCauseFailure(OperationBuilder operationBuilder, AssertableSnippets snippets) + throws IOException { new ResponseFieldsSnippet(Arrays.asList(fieldWithPath("a").description("one").type("a"))) - .document(this.operationBuilder.response() + .document(operationBuilder.response() .content("bar") .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_XML_VALUE) .build()); - assertThat(this.generatedSnippets.responseFields()) - .is(tableWithHeader("Path", "Type", "Description").row("`a`", "`a`", "one")); + assertThat(snippets.responseFields()) + .isTable((table) -> table.withHeader("Path", "Type", "Description").row("`a`", "`a`", "one")); } - @Test - public void additionalDescriptors() throws IOException { + @RenderedSnippetTest + void additionalDescriptors(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException { PayloadDocumentation .responseFields(fieldWithPath("id").description("one"), fieldWithPath("date").description("two"), fieldWithPath("assets").description("three")) .and(fieldWithPath("assets[]").description("four"), fieldWithPath("assets[].id").description("five"), fieldWithPath("assets[].name").description("six")) - .document(this.operationBuilder.response() + .document(operationBuilder.response() .content("{\"id\": 67,\"date\": \"2015-01-20\",\"assets\":" + " [{\"id\":356,\"name\": \"sample\"}]}") .build()); - assertThat(this.generatedSnippets.responseFields()) - .is(tableWithHeader("Path", "Type", "Description").row("`id`", "`Number`", "one") - .row("`date`", "`String`", "two") - .row("`assets`", "`Array`", "three") - .row("`assets[]`", "`Array`", "four") - .row("`assets[].id`", "`Number`", "five") - .row("`assets[].name`", "`String`", "six")); - } - - @Test - public void prefixedAdditionalDescriptors() throws IOException { + assertThat(snippets.responseFields()).isTable((table) -> table.withHeader("Path", "Type", "Description") + .row("`id`", "`Number`", "one") + .row("`date`", "`String`", "two") + .row("`assets`", "`Array`", "three") + .row("`assets[]`", "`Array`", "four") + .row("`assets[].id`", "`Number`", "five") + .row("`assets[].name`", "`String`", "six")); + } + + @RenderedSnippetTest + void prefixedAdditionalDescriptors(OperationBuilder operationBuilder, AssertableSnippets snippets) + throws IOException { PayloadDocumentation.responseFields(fieldWithPath("a").description("one")) .andWithPrefix("a.", fieldWithPath("b").description("two"), fieldWithPath("c").description("three")) - .document(this.operationBuilder.response().content("{\"a\": {\"b\": 5, \"c\": \"charlie\"}}").build()); - assertThat(this.generatedSnippets.responseFields()) - .is(tableWithHeader("Path", "Type", "Description").row("`a`", "`Object`", "one") - .row("`a.b`", "`Number`", "two") - .row("`a.c`", "`String`", "three")); + .document(operationBuilder.response().content("{\"a\": {\"b\": 5, \"c\": \"charlie\"}}").build()); + assertThat(snippets.responseFields()).isTable((table) -> table.withHeader("Path", "Type", "Description") + .row("`a`", "`Object`", "one") + .row("`a.b`", "`Number`", "two") + .row("`a.c`", "`String`", "three")); } - @Test - public void responseWithFieldsWithEscapedContent() throws IOException { + @RenderedSnippetTest + void responseWithFieldsWithEscapedContent(OperationBuilder operationBuilder, AssertableSnippets snippets) + throws IOException { new ResponseFieldsSnippet(Arrays.asList(fieldWithPath("Foo|Bar").type("one|two").description("three|four"))) - .document(this.operationBuilder.response().content("{\"Foo|Bar\": 5}").build()); - assertThat(this.generatedSnippets.responseFields()).is(tableWithHeader("Path", "Type", "Description") - .row(escapeIfNecessary("`Foo|Bar`"), escapeIfNecessary("`one|two`"), escapeIfNecessary("three|four"))); + .document(operationBuilder.response().content("{\"Foo|Bar\": 5}").build()); + assertThat(snippets.responseFields()).isTable( + (table) -> table.withHeader("Path", "Type", "Description").row("`Foo|Bar`", "`one|two`", "three|four")); } - @Test - public void mapResponseWithVaryingKeysMatchedUsingWildcard() throws IOException { + @RenderedSnippetTest + void mapResponseWithVaryingKeysMatchedUsingWildcard(OperationBuilder operationBuilder, AssertableSnippets snippets) + throws IOException { new ResponseFieldsSnippet(Arrays.asList(fieldWithPath("things.*.size").description("one"), fieldWithPath("things.*.type").description("two"))) - .document(this.operationBuilder.response() + .document(operationBuilder.response() .content("{\"things\": {\"12abf\": {\"type\":" + "\"Whale\", \"size\": \"HUGE\"}," + "\"gzM33\" : {\"type\": \"Screw\"," + "\"size\": \"SMALL\"}}}") .build()); - assertThat(this.generatedSnippets.responseFields()) - .is(tableWithHeader("Path", "Type", "Description").row("`things.*.size`", "`String`", "one") - .row("`things.*.type`", "`String`", "two")); + assertThat(snippets.responseFields()).isTable((table) -> table.withHeader("Path", "Type", "Description") + .row("`things.*.size`", "`String`", "one") + .row("`things.*.type`", "`String`", "two")); } - @Test - public void responseWithArrayContainingFieldThatIsSometimesNull() throws IOException { + @RenderedSnippetTest + void responseWithArrayContainingFieldThatIsSometimesNull(OperationBuilder operationBuilder, + AssertableSnippets snippets) throws IOException { new ResponseFieldsSnippet( Arrays.asList(fieldWithPath("assets[].name").description("one").type(JsonFieldType.STRING).optional())) - .document(this.operationBuilder.response() + .document(operationBuilder.response() .content("{\"assets\": [" + "{\"name\": \"sample1\"}, " + "{\"name\": null}, " + "{\"name\": \"sample2\"}]}") .build()); - assertThat(this.generatedSnippets.responseFields()) - .is(tableWithHeader("Path", "Type", "Description").row("`assets[].name`", "`String`", "one")); + assertThat(snippets.responseFields()).isTable( + (table) -> table.withHeader("Path", "Type", "Description").row("`assets[].name`", "`String`", "one")); } - @Test - public void optionalFieldBeneathArrayThatIsSometimesAbsent() throws IOException { + @RenderedSnippetTest + void optionalFieldBeneathArrayThatIsSometimesAbsent(OperationBuilder operationBuilder, AssertableSnippets snippets) + throws IOException { new ResponseFieldsSnippet( Arrays.asList(fieldWithPath("a[].b").description("one").type(JsonFieldType.NUMBER).optional(), fieldWithPath("a[].c").description("two").type(JsonFieldType.NUMBER))) - .document(this.operationBuilder.response() + .document(operationBuilder.response() .content("{\"a\":[{\"b\": 1,\"c\": 2}, " + "{\"c\": 2}, {\"b\": 1,\"c\": 2}]}") .build()); - assertThat(this.generatedSnippets.responseFields()) - .is(tableWithHeader("Path", "Type", "Description").row("`a[].b`", "`Number`", "one") - .row("`a[].c`", "`Number`", "two")); + assertThat(snippets.responseFields()).isTable((table) -> table.withHeader("Path", "Type", "Description") + .row("`a[].b`", "`Number`", "one") + .row("`a[].c`", "`Number`", "two")); } - @Test - public void typeDeterminationDoesNotSetTypeOnDescriptor() throws IOException { + @RenderedSnippetTest + void typeDeterminationDoesNotSetTypeOnDescriptor(OperationBuilder operationBuilder, AssertableSnippets snippets) + throws IOException { FieldDescriptor descriptor = fieldWithPath("id").description("one"); new ResponseFieldsSnippet(Arrays.asList(descriptor)) - .document(this.operationBuilder.response().content("{\"id\": 67}").build()); + .document(operationBuilder.response().content("{\"id\": 67}").build()); assertThat(descriptor.getType()).isNull(); - assertThat(this.generatedSnippets.responseFields()) - .is(tableWithHeader("Path", "Type", "Description").row("`id`", "`Number`", "one")); - } - - private String escapeIfNecessary(String input) { - if (this.templateFormat.getId().equals(TemplateFormats.markdown().getId())) { - return input; - } - return input.replace("|", "\\|"); + assertThat(snippets.responseFields()) + .isTable((table) -> table.withHeader("Path", "Type", "Description").row("`id`", "`Number`", "one")); + } + + @SnippetTest + void attemptToDocumentFieldsWithNoResponseBody(OperationBuilder operationBuilder) { + assertThatExceptionOfType(SnippetException.class) + .isThrownBy(() -> new ResponseFieldsSnippet(Arrays.asList(fieldWithPath("a").description("one"))) + .document(operationBuilder.build())) + .withMessage("Cannot document response fields as the response body is empty"); + } + + @SnippetTest + void fieldWithExplicitTypeThatDoesNotMatchThePayload(OperationBuilder operationBuilder) { + assertThatExceptionOfType(FieldTypesDoNotMatchException.class) + .isThrownBy(() -> new ResponseFieldsSnippet( + Arrays.asList(fieldWithPath("a").description("one").type(JsonFieldType.OBJECT))) + .document(operationBuilder.response().content("{ \"a\": 5 }").build())) + .withMessage("The documented type of the field 'a' is Object but the actual type is Number"); + } + + @SnippetTest + void fieldWithExplicitSpecificTypeThatActuallyVaries(OperationBuilder operationBuilder) { + assertThatExceptionOfType(FieldTypesDoNotMatchException.class) + .isThrownBy(() -> new ResponseFieldsSnippet( + Arrays.asList(fieldWithPath("[].a").description("one").type(JsonFieldType.OBJECT))) + .document(operationBuilder.response().content("[{ \"a\": 5 },{ \"a\": \"b\" }]").build())) + .withMessage("The documented type of the field '[].a' is Object but the actual type is Varies"); + } + + @SnippetTest + void undocumentedXmlResponseField(OperationBuilder operationBuilder) { + assertThatExceptionOfType(SnippetException.class) + .isThrownBy(() -> new ResponseFieldsSnippet(Collections.emptyList()) + .document(operationBuilder.response() + .content("5") + .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("foo") + .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("bar") + .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_XML_VALUE) + .build())) + .withMessage(String.format("The following parts of the payload were not documented:%nbar%n")); + } + + @SnippetTest + void xmlResponseFieldWithNoType(OperationBuilder operationBuilder) { + assertThatExceptionOfType(FieldTypeRequiredException.class) + .isThrownBy(() -> new ResponseFieldsSnippet(Arrays.asList(fieldWithPath("a").description("one"))) + .document(operationBuilder.response() + .content("5") + .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("") + .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("5") + .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.emptyList()) + .document(operationBuilder.response() + .content("Some plain text") + .header(HttpHeaders.CONTENT_TYPE, MediaType.TEXT_PLAIN_VALUE) + .build())) + .withMessage("Cannot handle content with type text/plain 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]"); } } diff --git a/spring-restdocs-core/src/test/java/org/springframework/restdocs/payload/XmlContentHandlerTests.java b/spring-restdocs-core/src/test/java/org/springframework/restdocs/payload/XmlContentHandlerTests.java index e95acf92..24411b7c 100644 --- a/spring-restdocs-core/src/test/java/org/springframework/restdocs/payload/XmlContentHandlerTests.java +++ b/spring-restdocs-core/src/test/java/org/springframework/restdocs/payload/XmlContentHandlerTests.java @@ -20,7 +20,7 @@ import java.util.Collections; import java.util.List; -import org.junit.Test; +import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; diff --git a/spring-restdocs-core/src/test/java/org/springframework/restdocs/request/FormParametersSnippetFailureTests.java b/spring-restdocs-core/src/test/java/org/springframework/restdocs/request/FormParametersSnippetFailureTests.java deleted file mode 100644 index 2180cb6f..00000000 --- a/spring-restdocs-core/src/test/java/org/springframework/restdocs/request/FormParametersSnippetFailureTests.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright 2014-present 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.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]"); - } - -} diff --git a/spring-restdocs-core/src/test/java/org/springframework/restdocs/request/FormParametersSnippetTests.java b/spring-restdocs-core/src/test/java/org/springframework/restdocs/request/FormParametersSnippetTests.java index d8590c3f..8f3934da 100644 --- a/spring-restdocs-core/src/test/java/org/springframework/restdocs/request/FormParametersSnippetTests.java +++ b/spring-restdocs-core/src/test/java/org/springframework/restdocs/request/FormParametersSnippetTests.java @@ -18,19 +18,17 @@ import java.io.IOException; import java.util.Arrays; +import java.util.Collections; -import org.junit.Test; - -import org.springframework.restdocs.AbstractSnippetTests; -import org.springframework.restdocs.templates.TemplateEngine; -import org.springframework.restdocs.templates.TemplateFormat; -import org.springframework.restdocs.templates.TemplateFormats; -import org.springframework.restdocs.templates.TemplateResourceResolver; -import org.springframework.restdocs.templates.mustache.MustacheTemplateEngine; +import org.springframework.restdocs.snippet.SnippetException; +import org.springframework.restdocs.testfixtures.jupiter.AssertableSnippets; +import org.springframework.restdocs.testfixtures.jupiter.OperationBuilder; +import org.springframework.restdocs.testfixtures.jupiter.RenderedSnippetTest; +import org.springframework.restdocs.testfixtures.jupiter.SnippetTemplate; +import org.springframework.restdocs.testfixtures.jupiter.SnippetTest; import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.BDDMockito.given; -import static org.mockito.Mockito.mock; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName; import static org.springframework.restdocs.snippet.Attributes.attributes; import static org.springframework.restdocs.snippet.Attributes.key; @@ -40,147 +38,151 @@ * * @author Andy Wilkinson */ -public class FormParametersSnippetTests extends AbstractSnippetTests { - - public FormParametersSnippetTests(String name, TemplateFormat templateFormat) { - super(name, templateFormat); - } +class FormParametersSnippetTests { - @Test - public void formParameters() throws IOException { + @RenderedSnippetTest + void formParameters(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException { new FormParametersSnippet( Arrays.asList(parameterWithName("a").description("one"), parameterWithName("b").description("two"))) - .document(this.operationBuilder.request("http://localhost").content("a=alpha&b=bravo").build()); - assertThat(this.generatedSnippets.formParameters()) - .is(tableWithHeader("Parameter", "Description").row("`a`", "one").row("`b`", "two")); + .document(operationBuilder.request("http://localhost").content("a=alpha&b=bravo").build()); + assertThat(snippets.formParameters()) + .isTable((table) -> table.withHeader("Parameter", "Description").row("`a`", "one").row("`b`", "two")); } - @Test - public void formParameterWithNoValue() throws IOException { + @RenderedSnippetTest + void formParameterWithNoValue(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException { new FormParametersSnippet(Arrays.asList(parameterWithName("a").description("one"))) - .document(this.operationBuilder.request("http://localhost").content("a=").build()); - assertThat(this.generatedSnippets.formParameters()) - .is(tableWithHeader("Parameter", "Description").row("`a`", "one")); + .document(operationBuilder.request("http://localhost").content("a=").build()); + assertThat(snippets.formParameters()) + .isTable((table) -> table.withHeader("Parameter", "Description").row("`a`", "one")); } - @Test - public void ignoredFormParameter() throws IOException { + @RenderedSnippetTest + void ignoredFormParameter(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException { new FormParametersSnippet( Arrays.asList(parameterWithName("a").ignored(), parameterWithName("b").description("two"))) - .document(this.operationBuilder.request("http://localhost").content("a=alpha&b=bravo").build()); - assertThat(this.generatedSnippets.formParameters()) - .is(tableWithHeader("Parameter", "Description").row("`b`", "two")); + .document(operationBuilder.request("http://localhost").content("a=alpha&b=bravo").build()); + assertThat(snippets.formParameters()) + .isTable((table) -> table.withHeader("Parameter", "Description").row("`b`", "two")); } - @Test - public void allUndocumentedFormParametersCanBeIgnored() throws IOException { + @RenderedSnippetTest + void allUndocumentedFormParametersCanBeIgnored(OperationBuilder operationBuilder, AssertableSnippets snippets) + throws IOException { new FormParametersSnippet(Arrays.asList(parameterWithName("b").description("two")), true) - .document(this.operationBuilder.request("http://localhost").content("a=alpha&b=bravo").build()); - assertThat(this.generatedSnippets.formParameters()) - .is(tableWithHeader("Parameter", "Description").row("`b`", "two")); + .document(operationBuilder.request("http://localhost").content("a=alpha&b=bravo").build()); + assertThat(snippets.formParameters()) + .isTable((table) -> table.withHeader("Parameter", "Description").row("`b`", "two")); } - @Test - public void missingOptionalFormParameter() throws IOException { + @RenderedSnippetTest + void missingOptionalFormParameter(OperationBuilder operationBuilder, AssertableSnippets snippets) + throws IOException { new FormParametersSnippet(Arrays.asList(parameterWithName("a").description("one").optional(), parameterWithName("b").description("two"))) - .document(this.operationBuilder.request("http://localhost").content("b=bravo").build()); - assertThat(this.generatedSnippets.formParameters()) - .is(tableWithHeader("Parameter", "Description").row("`a`", "one").row("`b`", "two")); + .document(operationBuilder.request("http://localhost").content("b=bravo").build()); + assertThat(snippets.formParameters()) + .isTable((table) -> table.withHeader("Parameter", "Description").row("`a`", "one").row("`b`", "two")); } - @Test - public void presentOptionalFormParameter() throws IOException { + @RenderedSnippetTest + void presentOptionalFormParameter(OperationBuilder operationBuilder, AssertableSnippets snippets) + throws IOException { new FormParametersSnippet(Arrays.asList(parameterWithName("a").description("one").optional())) - .document(this.operationBuilder.request("http://localhost").content("a=alpha").build()); - assertThat(this.generatedSnippets.formParameters()) - .is(tableWithHeader("Parameter", "Description").row("`a`", "one")); + .document(operationBuilder.request("http://localhost").content("a=alpha").build()); + assertThat(snippets.formParameters()) + .isTable((table) -> table.withHeader("Parameter", "Description").row("`a`", "one")); } - @Test - public void formParametersWithCustomAttributes() throws IOException { - TemplateResourceResolver resolver = mock(TemplateResourceResolver.class); - given(resolver.resolveTemplateResource("form-parameters")) - .willReturn(snippetResource("form-parameters-with-title")); + @RenderedSnippetTest + @SnippetTemplate(snippet = "form-parameters", template = "form-parameters-with-title") + void formParametersWithCustomAttributes(OperationBuilder operationBuilder, AssertableSnippets snippets) + throws IOException { new FormParametersSnippet( Arrays.asList(parameterWithName("a").description("one").attributes(key("foo").value("alpha")), parameterWithName("b").description("two").attributes(key("foo").value("bravo"))), attributes(key("title").value("The title"))) - .document(this.operationBuilder - .attribute(TemplateEngine.class.getName(), new MustacheTemplateEngine(resolver)) - .request("http://localhost") - .content("a=alpha&b=bravo") - .build()); - assertThat(this.generatedSnippets.formParameters()).contains("The title"); + .document(operationBuilder.request("http://localhost").content("a=alpha&b=bravo").build()); + assertThat(snippets.formParameters()).contains("The title"); } - @Test - public void formParametersWithCustomDescriptorAttributes() throws IOException { - TemplateResourceResolver resolver = mock(TemplateResourceResolver.class); - given(resolver.resolveTemplateResource("form-parameters")) - .willReturn(snippetResource("form-parameters-with-extra-column")); + @RenderedSnippetTest + @SnippetTemplate(snippet = "form-parameters", template = "form-parameters-with-extra-column") + void formParametersWithCustomDescriptorAttributes(OperationBuilder operationBuilder, AssertableSnippets snippets) + throws IOException { new FormParametersSnippet( Arrays.asList(parameterWithName("a").description("one").attributes(key("foo").value("alpha")), parameterWithName("b").description("two").attributes(key("foo").value("bravo")))) - .document(this.operationBuilder - .attribute(TemplateEngine.class.getName(), new MustacheTemplateEngine(resolver)) - .request("http://localhost") - .content("a=alpha&b=bravo") - .build()); - assertThat(this.generatedSnippets.formParameters()) - .is(tableWithHeader("Parameter", "Description", "Foo").row("a", "one", "alpha").row("b", "two", "bravo")); + .document(operationBuilder.request("http://localhost").content("a=alpha&b=bravo").build()); + assertThat(snippets.formParameters()).isTable((table) -> table.withHeader("Parameter", "Description", "Foo") + .row("a", "one", "alpha") + .row("b", "two", "bravo")); } - @Test - public void formParametersWithOptionalColumn() throws IOException { - TemplateResourceResolver resolver = mock(TemplateResourceResolver.class); - given(resolver.resolveTemplateResource("form-parameters")) - .willReturn(snippetResource("form-parameters-with-optional-column")); + @RenderedSnippetTest + @SnippetTemplate(snippet = "form-parameters", template = "form-parameters-with-optional-column") + void formParametersWithOptionalColumn(OperationBuilder operationBuilder, AssertableSnippets snippets) + throws IOException { new FormParametersSnippet(Arrays.asList(parameterWithName("a").description("one").optional(), parameterWithName("b").description("two"))) - .document(this.operationBuilder - .attribute(TemplateEngine.class.getName(), new MustacheTemplateEngine(resolver)) - .request("http://localhost") - .content("a=alpha&b=bravo") - .build()); - assertThat(this.generatedSnippets.formParameters()) - .is(tableWithHeader("Parameter", "Optional", "Description").row("a", "true", "one") + .document(operationBuilder.request("http://localhost").content("a=alpha&b=bravo").build()); + assertThat(snippets.formParameters()) + .isTable((table) -> table.withHeader("Parameter", "Optional", "Description") + .row("a", "true", "one") .row("b", "false", "two")); } - @Test - public void additionalDescriptors() throws IOException { + @RenderedSnippetTest + void additionalDescriptors(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException { RequestDocumentation.formParameters(parameterWithName("a").description("one")) .and(parameterWithName("b").description("two")) - .document(this.operationBuilder.request("http://localhost").content("a=alpha&b=bravo").build()); - assertThat(this.generatedSnippets.formParameters()) - .is(tableWithHeader("Parameter", "Description").row("`a`", "one").row("`b`", "two")); + .document(operationBuilder.request("http://localhost").content("a=alpha&b=bravo").build()); + assertThat(snippets.formParameters()) + .isTable((table) -> table.withHeader("Parameter", "Description").row("`a`", "one").row("`b`", "two")); } - @Test - public void additionalDescriptorsWithRelaxedFormParameters() throws IOException { + @RenderedSnippetTest + void additionalDescriptorsWithRelaxedFormParameters(OperationBuilder operationBuilder, AssertableSnippets snippets) + throws IOException { RequestDocumentation.relaxedFormParameters(parameterWithName("a").description("one")) .and(parameterWithName("b").description("two")) - .document(this.operationBuilder.request("http://localhost") - .content("a=alpha&b=bravo&c=undocumented") - .build()); - assertThat(this.generatedSnippets.formParameters()) - .is(tableWithHeader("Parameter", "Description").row("`a`", "one").row("`b`", "two")); + .document(operationBuilder.request("http://localhost").content("a=alpha&b=bravo&c=undocumented").build()); + assertThat(snippets.formParameters()) + .isTable((table) -> table.withHeader("Parameter", "Description").row("`a`", "one").row("`b`", "two")); } - @Test - public void formParametersWithEscapedContent() throws IOException { + @RenderedSnippetTest + void formParametersWithEscapedContent(OperationBuilder operationBuilder, AssertableSnippets snippets) + throws IOException { RequestDocumentation.formParameters(parameterWithName("Foo|Bar").description("one|two")) - .document(this.operationBuilder.request("http://localhost").content("Foo%7CBar=baz").build()); - assertThat(this.generatedSnippets.formParameters()).is(tableWithHeader("Parameter", "Description") - .row(escapeIfNecessary("`Foo|Bar`"), escapeIfNecessary("one|two"))); + .document(operationBuilder.request("http://localhost").content("Foo%7CBar=baz").build()); + assertThat(snippets.formParameters()) + .isTable((table) -> table.withHeader("Parameter", "Description").row("`Foo|Bar`", "one|two")); + } + + @SnippetTest + void undocumentedParameter(OperationBuilder operationBuilder) { + assertThatExceptionOfType(SnippetException.class) + .isThrownBy(() -> new FormParametersSnippet(Collections.emptyList()) + .document(operationBuilder.request("http://localhost").content("a=alpha").build())) + .withMessage("Form parameters with the following names were not documented: [a]"); + } + + @SnippetTest + void missingParameter(OperationBuilder operationBuilder) { + assertThatExceptionOfType(SnippetException.class) + .isThrownBy(() -> new FormParametersSnippet(Arrays.asList(parameterWithName("a").description("one"))) + .document(operationBuilder.request("http://localhost").build())) + .withMessage("Form parameters with the following names were not found in the request: [a]"); } - private String escapeIfNecessary(String input) { - if (this.templateFormat.getId().equals(TemplateFormats.markdown().getId())) { - return input; - } - return input.replace("|", "\\|"); + @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]"); } } diff --git a/spring-restdocs-core/src/test/java/org/springframework/restdocs/request/PathParametersSnippetFailureTests.java b/spring-restdocs-core/src/test/java/org/springframework/restdocs/request/PathParametersSnippetFailureTests.java deleted file mode 100644 index 2efb67d4..00000000 --- a/spring-restdocs-core/src/test/java/org/springframework/restdocs/request/PathParametersSnippetFailureTests.java +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright 2014-present 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.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]"); - } - -} diff --git a/spring-restdocs-core/src/test/java/org/springframework/restdocs/request/PathParametersSnippetTests.java b/spring-restdocs-core/src/test/java/org/springframework/restdocs/request/PathParametersSnippetTests.java index 5cda9425..66099423 100644 --- a/spring-restdocs-core/src/test/java/org/springframework/restdocs/request/PathParametersSnippetTests.java +++ b/spring-restdocs-core/src/test/java/org/springframework/restdocs/request/PathParametersSnippetTests.java @@ -18,20 +18,20 @@ import java.io.IOException; import java.util.Arrays; +import java.util.Collections; -import org.junit.Test; - -import org.springframework.restdocs.AbstractSnippetTests; import org.springframework.restdocs.generate.RestDocumentationGenerator; -import org.springframework.restdocs.templates.TemplateEngine; +import org.springframework.restdocs.snippet.SnippetException; import org.springframework.restdocs.templates.TemplateFormat; import org.springframework.restdocs.templates.TemplateFormats; -import org.springframework.restdocs.templates.TemplateResourceResolver; -import org.springframework.restdocs.templates.mustache.MustacheTemplateEngine; +import org.springframework.restdocs.testfixtures.jupiter.AssertableSnippets; +import org.springframework.restdocs.testfixtures.jupiter.OperationBuilder; +import org.springframework.restdocs.testfixtures.jupiter.RenderedSnippetTest; +import org.springframework.restdocs.testfixtures.jupiter.SnippetTemplate; +import org.springframework.restdocs.testfixtures.jupiter.SnippetTest; import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.BDDMockito.given; -import static org.mockito.Mockito.mock; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName; import static org.springframework.restdocs.snippet.Attributes.attributes; import static org.springframework.restdocs.snippet.Attributes.key; @@ -41,164 +41,192 @@ * * @author Andy Wilkinson */ -public class PathParametersSnippetTests extends AbstractSnippetTests { - - public PathParametersSnippetTests(String name, TemplateFormat templateFormat) { - super(name, templateFormat); - } +class PathParametersSnippetTests { - @Test - public void pathParameters() throws IOException { + @RenderedSnippetTest + void pathParameters(OperationBuilder operationBuilder, AssertableSnippets snippets, TemplateFormat templateFormat) + throws IOException { new PathParametersSnippet( Arrays.asList(parameterWithName("a").description("one"), parameterWithName("b").description("two"))) - .document( - this.operationBuilder.attribute(RestDocumentationGenerator.ATTRIBUTE_NAME_URL_TEMPLATE, "/{a}/{b}") - .build()); - assertThat(this.generatedSnippets.pathParameters()) - .is(tableWithTitleAndHeader(getTitle(), "Parameter", "Description").row("`a`", "one").row("`b`", "two")); + .document(operationBuilder.attribute(RestDocumentationGenerator.ATTRIBUTE_NAME_URL_TEMPLATE, "/{a}/{b}") + .build()); + assertThat(snippets.pathParameters()) + .isTable((table) -> table.withTitleAndHeader(getTitle(templateFormat), "Parameter", "Description") + .row("`a`", "one") + .row("`b`", "two")); } - @Test - public void ignoredPathParameter() throws IOException { + @RenderedSnippetTest + void ignoredPathParameter(OperationBuilder operationBuilder, AssertableSnippets snippets, + TemplateFormat templateFormat) 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")); + .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")); } - @Test - public void allUndocumentedPathParametersCanBeIgnored() throws IOException { + @RenderedSnippetTest + void allUndocumentedPathParametersCanBeIgnored(OperationBuilder operationBuilder, AssertableSnippets snippets, + TemplateFormat templateFormat) 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")); + operationBuilder.attribute(RestDocumentationGenerator.ATTRIBUTE_NAME_URL_TEMPLATE, "/{a}/{b}").build()); + assertThat(snippets.pathParameters()) + .isTable((table) -> table.withTitleAndHeader(getTitle(templateFormat), "Parameter", "Description") + .row("`b`", "two")); } - @Test - public void missingOptionalPathParameter() throws IOException { + @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(this.operationBuilder.attribute(RestDocumentationGenerator.ATTRIBUTE_NAME_URL_TEMPLATE, "/{a}") - .build()); - assertThat(this.generatedSnippets.pathParameters()) - .is(tableWithTitleAndHeader(getTitle("/{a}"), "Parameter", "Description").row("`a`", "one") + .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")); } - @Test - public void presentOptionalPathParameter() throws IOException { - new PathParametersSnippet(Arrays.asList(parameterWithName("a").description("one").optional())) - .document(this.operationBuilder.attribute(RestDocumentationGenerator.ATTRIBUTE_NAME_URL_TEMPLATE, "/{a}") - .build()); - assertThat(this.generatedSnippets.pathParameters()) - .is(tableWithTitleAndHeader(getTitle("/{a}"), "Parameter", "Description").row("`a`", "one")); + @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")); } - @Test - public void pathParametersWithQueryString() throws IOException { + @RenderedSnippetTest + void pathParametersWithQueryString(OperationBuilder operationBuilder, AssertableSnippets snippets, + TemplateFormat templateFormat) throws IOException { new PathParametersSnippet( Arrays.asList(parameterWithName("a").description("one"), parameterWithName("b").description("two"))) - .document(this.operationBuilder + .document(operationBuilder .attribute(RestDocumentationGenerator.ATTRIBUTE_NAME_URL_TEMPLATE, "/{a}/{b}?foo=bar") .build()); - assertThat(this.generatedSnippets.pathParameters()) - .is(tableWithTitleAndHeader(getTitle(), "Parameter", "Description").row("`a`", "one").row("`b`", "two")); + assertThat(snippets.pathParameters()) + .isTable((table) -> table.withTitleAndHeader(getTitle(templateFormat), "Parameter", "Description") + .row("`a`", "one") + .row("`b`", "two")); } - @Test - public void pathParametersWithQueryStringWithParameters() throws IOException { + @RenderedSnippetTest + void pathParametersWithQueryStringWithParameters(OperationBuilder operationBuilder, AssertableSnippets snippets, + TemplateFormat templateFormat) throws IOException { new PathParametersSnippet( Arrays.asList(parameterWithName("a").description("one"), parameterWithName("b").description("two"))) - .document(this.operationBuilder + .document(operationBuilder .attribute(RestDocumentationGenerator.ATTRIBUTE_NAME_URL_TEMPLATE, "/{a}/{b}?foo={c}") .build()); - assertThat(this.generatedSnippets.pathParameters()) - .is(tableWithTitleAndHeader(getTitle(), "Parameter", "Description").row("`a`", "one").row("`b`", "two")); + assertThat(snippets.pathParameters()) + .isTable((table) -> table.withTitleAndHeader(getTitle(templateFormat), "Parameter", "Description") + .row("`a`", "one") + .row("`b`", "two")); } - @Test - public void pathParametersWithCustomAttributes() throws IOException { - TemplateResourceResolver resolver = mock(TemplateResourceResolver.class); - given(resolver.resolveTemplateResource("path-parameters")) - .willReturn(snippetResource("path-parameters-with-title")); + @RenderedSnippetTest + @SnippetTemplate(snippet = "path-parameters", template = "path-parameters-with-title") + void pathParametersWithCustomAttributes(OperationBuilder operationBuilder, AssertableSnippets snippets, + TemplateFormat templateFormat) throws IOException { new PathParametersSnippet( Arrays.asList(parameterWithName("a").description("one").attributes(key("foo").value("alpha")), parameterWithName("b").description("two").attributes(key("foo").value("bravo"))), attributes(key("title").value("The title"))) - .document( - this.operationBuilder.attribute(RestDocumentationGenerator.ATTRIBUTE_NAME_URL_TEMPLATE, "/{a}/{b}") - .attribute(TemplateEngine.class.getName(), new MustacheTemplateEngine(resolver)) - .build()); - assertThat(this.generatedSnippets.pathParameters()).contains("The title"); + .document(operationBuilder.attribute(RestDocumentationGenerator.ATTRIBUTE_NAME_URL_TEMPLATE, "/{a}/{b}") + .build()); + assertThat(snippets.pathParameters()).contains("The title"); } - @Test - public void pathParametersWithCustomDescriptorAttributes() throws IOException { - TemplateResourceResolver resolver = mock(TemplateResourceResolver.class); - given(resolver.resolveTemplateResource("path-parameters")) - .willReturn(snippetResource("path-parameters-with-extra-column")); + @RenderedSnippetTest + @SnippetTemplate(snippet = "path-parameters", template = "path-parameters-with-extra-column") + void pathParametersWithCustomDescriptorAttributes(OperationBuilder operationBuilder, AssertableSnippets snippets, + TemplateFormat templateFormat) throws IOException { new PathParametersSnippet( Arrays.asList(parameterWithName("a").description("one").attributes(key("foo").value("alpha")), parameterWithName("b").description("two").attributes(key("foo").value("bravo")))) - .document( - this.operationBuilder.attribute(RestDocumentationGenerator.ATTRIBUTE_NAME_URL_TEMPLATE, "/{a}/{b}") - .attribute(TemplateEngine.class.getName(), new MustacheTemplateEngine(resolver)) - .build()); - assertThat(this.generatedSnippets.pathParameters()) - .is(tableWithHeader("Parameter", "Description", "Foo").row("a", "one", "alpha").row("b", "two", "bravo")); + .document(operationBuilder.attribute(RestDocumentationGenerator.ATTRIBUTE_NAME_URL_TEMPLATE, "/{a}/{b}") + .build()); + assertThat(snippets.pathParameters()).isTable((table) -> table.withHeader("Parameter", "Description", "Foo") + .row("a", "one", "alpha") + .row("b", "two", "bravo")); } - @Test - public void additionalDescriptors() throws IOException { + @RenderedSnippetTest + void additionalDescriptors(OperationBuilder operationBuilder, AssertableSnippets snippets, + TemplateFormat templateFormat) throws IOException { RequestDocumentation.pathParameters(parameterWithName("a").description("one")) .and(parameterWithName("b").description("two")) - .document( - this.operationBuilder.attribute(RestDocumentationGenerator.ATTRIBUTE_NAME_URL_TEMPLATE, "/{a}/{b}") - .build()); - assertThat(this.generatedSnippets.pathParameters()) - .is(tableWithTitleAndHeader(getTitle(), "Parameter", "Description").row("`a`", "one").row("`b`", "two")); + .document(operationBuilder.attribute(RestDocumentationGenerator.ATTRIBUTE_NAME_URL_TEMPLATE, "/{a}/{b}") + .build()); + assertThat(snippets.pathParameters()).isTable( + (table) -> table.withTitleAndHeader(getTitle(templateFormat, "/{a}/{b}"), "Parameter", "Description") + .row("`a`", "one") + .row("`b`", "two")); } - @Test - public void additionalDescriptorsWithRelaxedRequestParameters() throws IOException { + @RenderedSnippetTest + void additionalDescriptorsWithRelaxedRequestParameters(OperationBuilder operationBuilder, + AssertableSnippets snippets, TemplateFormat templateFormat) throws IOException { RequestDocumentation.relaxedPathParameters(parameterWithName("a").description("one")) .and(parameterWithName("b").description("two")) - .document(this.operationBuilder - .attribute(RestDocumentationGenerator.ATTRIBUTE_NAME_URL_TEMPLATE, "/{a}/{b}/{c}") + .document(operationBuilder.attribute(RestDocumentationGenerator.ATTRIBUTE_NAME_URL_TEMPLATE, "/{a}/{b}/{c}") .build()); - assertThat(this.generatedSnippets.pathParameters()) - .is(tableWithTitleAndHeader(getTitle("/{a}/{b}/{c}"), "Parameter", "Description").row("`a`", "one") - .row("`b`", "two")); + assertThat(snippets.pathParameters()).isTable((table) -> table + .withTitleAndHeader(getTitle(templateFormat, "/{a}/{b}/{c}"), "Parameter", "Description") + .row("`a`", "one") + .row("`b`", "two")); } - @Test - public void pathParametersWithEscapedContent() throws IOException { + @RenderedSnippetTest + void pathParametersWithEscapedContent(OperationBuilder operationBuilder, AssertableSnippets snippets, + TemplateFormat templateFormat) throws IOException { RequestDocumentation.pathParameters(parameterWithName("Foo|Bar").description("one|two")) - .document( - this.operationBuilder.attribute(RestDocumentationGenerator.ATTRIBUTE_NAME_URL_TEMPLATE, "{Foo|Bar}") - .build()); - assertThat(this.generatedSnippets.pathParameters()) - .is(tableWithTitleAndHeader(getTitle("{Foo|Bar}"), "Parameter", "Description") - .row(escapeIfNecessary("`Foo|Bar`"), escapeIfNecessary("one|two"))); + .document(operationBuilder.attribute(RestDocumentationGenerator.ATTRIBUTE_NAME_URL_TEMPLATE, "{Foo|Bar}") + .build()); + assertThat(snippets.pathParameters()).isTable( + (table) -> table.withTitleAndHeader(getTitle(templateFormat, "{Foo|Bar}"), "Parameter", "Description") + .row("`Foo|Bar`", "one|two")); } - private String escapeIfNecessary(String input) { - if (this.templateFormat.getId().equals(TemplateFormats.markdown().getId())) { - return input; - } - return input.replace("|", "\\|"); + @SnippetTest + void undocumentedPathParameter(OperationBuilder operationBuilder) { + assertThatExceptionOfType(SnippetException.class) + .isThrownBy(() -> new PathParametersSnippet(Collections.emptyList()) + .document(operationBuilder.attribute(RestDocumentationGenerator.ATTRIBUTE_NAME_URL_TEMPLATE, "/{a}/") + .build())) + .withMessage("Path parameters with the following names were not documented: [a]"); + } + + @SnippetTest + void missingPathParameter(OperationBuilder operationBuilder) { + assertThatExceptionOfType(SnippetException.class) + .isThrownBy(() -> new PathParametersSnippet(Arrays.asList(parameterWithName("a").description("one"))) + .document(operationBuilder.attribute(RestDocumentationGenerator.ATTRIBUTE_NAME_URL_TEMPLATE, "/") + .build())) + .withMessage("Path parameters with the following names were not found in the request: [a]"); + } + + @SnippetTest + void undocumentedAndMissingPathParameters(OperationBuilder operationBuilder) { + assertThatExceptionOfType(SnippetException.class) + .isThrownBy(() -> new PathParametersSnippet(Arrays.asList(parameterWithName("a").description("one"))) + .document(operationBuilder.attribute(RestDocumentationGenerator.ATTRIBUTE_NAME_URL_TEMPLATE, "/{b}") + .build())) + .withMessage("Path parameters with the following names were not documented: [b]. Path parameters with the" + + " following names were not found in the request: [a]"); } - private String getTitle() { - return getTitle("/{a}/{b}"); + private String getTitle(TemplateFormat templateFormat) { + return getTitle(templateFormat, "/{a}/{b}"); } - private String getTitle(String title) { - if (this.templateFormat.getId().equals(TemplateFormats.asciidoctor().getId())) { + private String getTitle(TemplateFormat templateFormat, String title) { + if (templateFormat.getId().equals(TemplateFormats.asciidoctor().getId())) { return "+" + title + "+"; } return "`" + title + "`"; diff --git a/spring-restdocs-core/src/test/java/org/springframework/restdocs/request/QueryParametersSnippetFailureTests.java b/spring-restdocs-core/src/test/java/org/springframework/restdocs/request/QueryParametersSnippetFailureTests.java deleted file mode 100644 index f50475b7..00000000 --- a/spring-restdocs-core/src/test/java/org/springframework/restdocs/request/QueryParametersSnippetFailureTests.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright 2014-present 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.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]"); - } - -} diff --git a/spring-restdocs-core/src/test/java/org/springframework/restdocs/request/QueryParametersSnippetTests.java b/spring-restdocs-core/src/test/java/org/springframework/restdocs/request/QueryParametersSnippetTests.java index 73dc70dc..efe65e3b 100644 --- a/spring-restdocs-core/src/test/java/org/springframework/restdocs/request/QueryParametersSnippetTests.java +++ b/spring-restdocs-core/src/test/java/org/springframework/restdocs/request/QueryParametersSnippetTests.java @@ -18,19 +18,17 @@ import java.io.IOException; import java.util.Arrays; +import java.util.Collections; -import org.junit.Test; - -import org.springframework.restdocs.AbstractSnippetTests; -import org.springframework.restdocs.templates.TemplateEngine; -import org.springframework.restdocs.templates.TemplateFormat; -import org.springframework.restdocs.templates.TemplateFormats; -import org.springframework.restdocs.templates.TemplateResourceResolver; -import org.springframework.restdocs.templates.mustache.MustacheTemplateEngine; +import org.springframework.restdocs.snippet.SnippetException; +import org.springframework.restdocs.testfixtures.jupiter.AssertableSnippets; +import org.springframework.restdocs.testfixtures.jupiter.OperationBuilder; +import org.springframework.restdocs.testfixtures.jupiter.RenderedSnippetTest; +import org.springframework.restdocs.testfixtures.jupiter.SnippetTemplate; +import org.springframework.restdocs.testfixtures.jupiter.SnippetTest; import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.BDDMockito.given; -import static org.mockito.Mockito.mock; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName; import static org.springframework.restdocs.snippet.Attributes.attributes; import static org.springframework.restdocs.snippet.Attributes.key; @@ -40,142 +38,151 @@ * * @author Andy Wilkinson */ -public class QueryParametersSnippetTests extends AbstractSnippetTests { - - public QueryParametersSnippetTests(String name, TemplateFormat templateFormat) { - super(name, templateFormat); - } +class QueryParametersSnippetTests { - @Test - public void queryParameters() throws IOException { + @RenderedSnippetTest + void queryParameters(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException { new QueryParametersSnippet( Arrays.asList(parameterWithName("a").description("one"), parameterWithName("b").description("two"))) - .document(this.operationBuilder.request("http://localhost?a=alpha&b=bravo").build()); - assertThat(this.generatedSnippets.queryParameters()) - .is(tableWithHeader("Parameter", "Description").row("`a`", "one").row("`b`", "two")); + .document(operationBuilder.request("http://localhost?a=alpha&b=bravo").build()); + assertThat(snippets.queryParameters()) + .isTable((table) -> table.withHeader("Parameter", "Description").row("`a`", "one").row("`b`", "two")); } - @Test - public void queryParameterWithNoValue() throws IOException { + @RenderedSnippetTest + void queryParameterWithNoValue(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException { new QueryParametersSnippet(Arrays.asList(parameterWithName("a").description("one"))) - .document(this.operationBuilder.request("http://localhost?a").build()); - assertThat(this.generatedSnippets.queryParameters()) - .is(tableWithHeader("Parameter", "Description").row("`a`", "one")); + .document(operationBuilder.request("http://localhost?a").build()); + assertThat(snippets.queryParameters()) + .isTable((table) -> table.withHeader("Parameter", "Description").row("`a`", "one")); } - @Test - public void ignoredQueryParameter() throws IOException { + @RenderedSnippetTest + void ignoredQueryParameter(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException { new QueryParametersSnippet( Arrays.asList(parameterWithName("a").ignored(), parameterWithName("b").description("two"))) - .document(this.operationBuilder.request("http://localhost?a=alpha&b=bravo").build()); - assertThat(this.generatedSnippets.queryParameters()) - .is(tableWithHeader("Parameter", "Description").row("`b`", "two")); + .document(operationBuilder.request("http://localhost?a=alpha&b=bravo").build()); + assertThat(snippets.queryParameters()) + .isTable((table) -> table.withHeader("Parameter", "Description").row("`b`", "two")); } - @Test - public void allUndocumentedQueryParametersCanBeIgnored() throws IOException { + @RenderedSnippetTest + void allUndocumentedQueryParametersCanBeIgnored(OperationBuilder operationBuilder, AssertableSnippets snippets) + throws IOException { new QueryParametersSnippet(Arrays.asList(parameterWithName("b").description("two")), true) - .document(this.operationBuilder.request("http://localhost?a=alpha&b=bravo").build()); - assertThat(this.generatedSnippets.queryParameters()) - .is(tableWithHeader("Parameter", "Description").row("`b`", "two")); + .document(operationBuilder.request("http://localhost?a=alpha&b=bravo").build()); + assertThat(snippets.queryParameters()) + .isTable((table) -> table.withHeader("Parameter", "Description").row("`b`", "two")); } - @Test - public void missingOptionalQueryParameter() throws IOException { + @RenderedSnippetTest + void missingOptionalQueryParameter(OperationBuilder operationBuilder, AssertableSnippets snippets) + throws IOException { new QueryParametersSnippet(Arrays.asList(parameterWithName("a").description("one").optional(), parameterWithName("b").description("two"))) - .document(this.operationBuilder.request("http://localhost?b=bravo").build()); - assertThat(this.generatedSnippets.queryParameters()) - .is(tableWithHeader("Parameter", "Description").row("`a`", "one").row("`b`", "two")); + .document(operationBuilder.request("http://localhost?b=bravo").build()); + assertThat(snippets.queryParameters()) + .isTable((table) -> table.withHeader("Parameter", "Description").row("`a`", "one").row("`b`", "two")); } - @Test - public void presentOptionalQueryParameter() throws IOException { + @RenderedSnippetTest + void presentOptionalQueryParameter(OperationBuilder operationBuilder, AssertableSnippets snippets) + throws IOException { new QueryParametersSnippet(Arrays.asList(parameterWithName("a").description("one").optional())) - .document(this.operationBuilder.request("http://localhost?a=alpha").build()); - assertThat(this.generatedSnippets.queryParameters()) - .is(tableWithHeader("Parameter", "Description").row("`a`", "one")); + .document(operationBuilder.request("http://localhost?a=alpha").build()); + assertThat(snippets.queryParameters()) + .isTable((table) -> table.withHeader("Parameter", "Description").row("`a`", "one")); } - @Test - public void queryParametersWithCustomAttributes() throws IOException { - TemplateResourceResolver resolver = mock(TemplateResourceResolver.class); - given(resolver.resolveTemplateResource("query-parameters")) - .willReturn(snippetResource("query-parameters-with-title")); + @RenderedSnippetTest + @SnippetTemplate(snippet = "query-parameters", template = "query-parameters-with-title") + void queryParametersWithCustomAttributes(OperationBuilder operationBuilder, AssertableSnippets snippets) + throws IOException { new QueryParametersSnippet( Arrays.asList(parameterWithName("a").description("one").attributes(key("foo").value("alpha")), parameterWithName("b").description("two").attributes(key("foo").value("bravo"))), attributes(key("title").value("The title"))) - .document(this.operationBuilder - .attribute(TemplateEngine.class.getName(), new MustacheTemplateEngine(resolver)) - .request("http://localhost?a=alpha&b=bravo") - .build()); - assertThat(this.generatedSnippets.queryParameters()).contains("The title"); + .document(operationBuilder.request("http://localhost?a=alpha&b=bravo").build()); + assertThat(snippets.queryParameters()).contains("The title"); } - @Test - public void queryParametersWithCustomDescriptorAttributes() throws IOException { - TemplateResourceResolver resolver = mock(TemplateResourceResolver.class); - given(resolver.resolveTemplateResource("query-parameters")) - .willReturn(snippetResource("query-parameters-with-extra-column")); + @RenderedSnippetTest + @SnippetTemplate(snippet = "query-parameters", template = "query-parameters-with-extra-column") + void queryParametersWithCustomDescriptorAttributes(OperationBuilder operationBuilder, AssertableSnippets snippets) + throws IOException { new QueryParametersSnippet( Arrays.asList(parameterWithName("a").description("one").attributes(key("foo").value("alpha")), parameterWithName("b").description("two").attributes(key("foo").value("bravo")))) - .document(this.operationBuilder - .attribute(TemplateEngine.class.getName(), new MustacheTemplateEngine(resolver)) - .request("http://localhost?a=alpha&b=bravo") - .build()); - assertThat(this.generatedSnippets.queryParameters()) - .is(tableWithHeader("Parameter", "Description", "Foo").row("a", "one", "alpha").row("b", "two", "bravo")); + .document(operationBuilder.request("http://localhost?a=alpha&b=bravo").build()); + assertThat(snippets.queryParameters()).isTable((table) -> table.withHeader("Parameter", "Description", "Foo") + .row("a", "one", "alpha") + .row("b", "two", "bravo")); } - @Test - public void queryParametersWithOptionalColumn() throws IOException { - TemplateResourceResolver resolver = mock(TemplateResourceResolver.class); - given(resolver.resolveTemplateResource("query-parameters")) - .willReturn(snippetResource("query-parameters-with-optional-column")); + @RenderedSnippetTest + @SnippetTemplate(snippet = "query-parameters", template = "query-parameters-with-optional-column") + void queryParametersWithOptionalColumn(OperationBuilder operationBuilder, AssertableSnippets snippets) + throws IOException { new QueryParametersSnippet(Arrays.asList(parameterWithName("a").description("one").optional(), parameterWithName("b").description("two"))) - .document(this.operationBuilder - .attribute(TemplateEngine.class.getName(), new MustacheTemplateEngine(resolver)) - .request("http://localhost?a=alpha&b=bravo") - .build()); - assertThat(this.generatedSnippets.queryParameters()) - .is(tableWithHeader("Parameter", "Optional", "Description").row("a", "true", "one") + .document(operationBuilder.request("http://localhost?a=alpha&b=bravo").build()); + assertThat(snippets.queryParameters()) + .isTable((table) -> table.withHeader("Parameter", "Optional", "Description") + .row("a", "true", "one") .row("b", "false", "two")); } - @Test - public void additionalDescriptors() throws IOException { + @RenderedSnippetTest + void additionalDescriptors(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException { RequestDocumentation.queryParameters(parameterWithName("a").description("one")) .and(parameterWithName("b").description("two")) - .document(this.operationBuilder.request("http://localhost?a=alpha&b=bravo").build()); - assertThat(this.generatedSnippets.queryParameters()) - .is(tableWithHeader("Parameter", "Description").row("`a`", "one").row("`b`", "two")); + .document(operationBuilder.request("http://localhost?a=alpha&b=bravo").build()); + assertThat(snippets.queryParameters()) + .isTable((table) -> table.withHeader("Parameter", "Description").row("`a`", "one").row("`b`", "two")); } - @Test - public void additionalDescriptorsWithRelaxedQueryParameters() throws IOException { + @RenderedSnippetTest + void additionalDescriptorsWithRelaxedQueryParameters(OperationBuilder operationBuilder, AssertableSnippets snippets) + throws IOException { RequestDocumentation.relaxedQueryParameters(parameterWithName("a").description("one")) .and(parameterWithName("b").description("two")) - .document(this.operationBuilder.request("http://localhost?a=alpha&b=bravo&c=undocumented").build()); - assertThat(this.generatedSnippets.queryParameters()) - .is(tableWithHeader("Parameter", "Description").row("`a`", "one").row("`b`", "two")); + .document(operationBuilder.request("http://localhost?a=alpha&b=bravo&c=undocumented").build()); + assertThat(snippets.queryParameters()) + .isTable((table) -> table.withHeader("Parameter", "Description").row("`a`", "one").row("`b`", "two")); } - @Test - public void queryParametersWithEscapedContent() throws IOException { + @RenderedSnippetTest + void queryParametersWithEscapedContent(OperationBuilder operationBuilder, AssertableSnippets snippets) + throws IOException { RequestDocumentation.queryParameters(parameterWithName("Foo|Bar").description("one|two")) - .document(this.operationBuilder.request("http://localhost?Foo%7CBar=baz").build()); - assertThat(this.generatedSnippets.queryParameters()).is(tableWithHeader("Parameter", "Description") - .row(escapeIfNecessary("`Foo|Bar`"), escapeIfNecessary("one|two"))); + .document(operationBuilder.request("http://localhost?Foo%7CBar=baz").build()); + assertThat(snippets.queryParameters()) + .isTable((table) -> table.withHeader("Parameter", "Description").row("`Foo|Bar`", "one|two")); + } + + @SnippetTest + void undocumentedParameter(OperationBuilder operationBuilder) { + assertThatExceptionOfType(SnippetException.class) + .isThrownBy(() -> new QueryParametersSnippet(Collections.emptyList()) + .document(operationBuilder.request("http://localhost?a=alpha").build())) + .withMessage("Query parameters with the following names were not documented: [a]"); + } + + @SnippetTest + void missingParameter(OperationBuilder operationBuilder) { + assertThatExceptionOfType(SnippetException.class) + .isThrownBy(() -> new QueryParametersSnippet(Arrays.asList(parameterWithName("a").description("one"))) + .document(operationBuilder.request("http://localhost").build())) + .withMessage("Query parameters with the following names were not found in the request: [a]"); } - private String escapeIfNecessary(String input) { - if (this.templateFormat.getId().equals(TemplateFormats.markdown().getId())) { - return input; - } - return input.replace("|", "\\|"); + @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]"); } } diff --git a/spring-restdocs-core/src/test/java/org/springframework/restdocs/request/RequestPartsSnippetFailureTests.java b/spring-restdocs-core/src/test/java/org/springframework/restdocs/request/RequestPartsSnippetFailureTests.java deleted file mode 100644 index 74b12c1b..00000000 --- a/spring-restdocs-core/src/test/java/org/springframework/restdocs/request/RequestPartsSnippetFailureTests.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright 2014-present 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.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]"); - } - -} diff --git a/spring-restdocs-core/src/test/java/org/springframework/restdocs/request/RequestPartsSnippetTests.java b/spring-restdocs-core/src/test/java/org/springframework/restdocs/request/RequestPartsSnippetTests.java index 22b8317c..041c2621 100644 --- a/spring-restdocs-core/src/test/java/org/springframework/restdocs/request/RequestPartsSnippetTests.java +++ b/spring-restdocs-core/src/test/java/org/springframework/restdocs/request/RequestPartsSnippetTests.java @@ -18,19 +18,17 @@ import java.io.IOException; import java.util.Arrays; +import java.util.Collections; -import org.junit.Test; - -import org.springframework.restdocs.AbstractSnippetTests; -import org.springframework.restdocs.templates.TemplateEngine; -import org.springframework.restdocs.templates.TemplateFormat; -import org.springframework.restdocs.templates.TemplateFormats; -import org.springframework.restdocs.templates.TemplateResourceResolver; -import org.springframework.restdocs.templates.mustache.MustacheTemplateEngine; +import org.springframework.restdocs.snippet.SnippetException; +import org.springframework.restdocs.testfixtures.jupiter.AssertableSnippets; +import org.springframework.restdocs.testfixtures.jupiter.OperationBuilder; +import org.springframework.restdocs.testfixtures.jupiter.RenderedSnippetTest; +import org.springframework.restdocs.testfixtures.jupiter.SnippetTemplate; +import org.springframework.restdocs.testfixtures.jupiter.SnippetTest; import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.BDDMockito.given; -import static org.mockito.Mockito.mock; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.springframework.restdocs.request.RequestDocumentation.partWithName; import static org.springframework.restdocs.snippet.Attributes.attributes; import static org.springframework.restdocs.snippet.Attributes.key; @@ -40,145 +38,157 @@ * * @author Andy Wilkinson */ -public class RequestPartsSnippetTests extends AbstractSnippetTests { - - public RequestPartsSnippetTests(String name, TemplateFormat templateFormat) { - super(name, templateFormat); - } +class RequestPartsSnippetTests { - @Test - public void requestParts() throws IOException { + @RenderedSnippetTest + void requestParts(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException { new RequestPartsSnippet( Arrays.asList(partWithName("a").description("one"), partWithName("b").description("two"))) - .document(this.operationBuilder.request("http://localhost") + .document(operationBuilder.request("http://localhost") .part("a", "bravo".getBytes()) .and() .part("b", "bravo".getBytes()) .build()); - assertThat(this.generatedSnippets.requestParts()) - .is(tableWithHeader("Part", "Description").row("`a`", "one").row("`b`", "two")); + assertThat(snippets.requestParts()) + .isTable((table) -> table.withHeader("Part", "Description").row("`a`", "one").row("`b`", "two")); } - @Test - public void ignoredRequestPart() throws IOException { + @RenderedSnippetTest + void ignoredRequestPart(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException { new RequestPartsSnippet(Arrays.asList(partWithName("a").ignored(), partWithName("b").description("two"))) - .document(this.operationBuilder.request("http://localhost") + .document(operationBuilder.request("http://localhost") .part("a", "bravo".getBytes()) .and() .part("b", "bravo".getBytes()) .build()); - assertThat(this.generatedSnippets.requestParts()).is(tableWithHeader("Part", "Description").row("`b`", "two")); + assertThat(snippets.requestParts()) + .isTable((table) -> table.withHeader("Part", "Description").row("`b`", "two")); } - @Test - public void allUndocumentedRequestPartsCanBeIgnored() throws IOException { + @RenderedSnippetTest + void allUndocumentedRequestPartsCanBeIgnored(OperationBuilder operationBuilder, AssertableSnippets snippets) + throws IOException { new RequestPartsSnippet(Arrays.asList(partWithName("b").description("two")), true) - .document(this.operationBuilder.request("http://localhost") + .document(operationBuilder.request("http://localhost") .part("a", "bravo".getBytes()) .and() .part("b", "bravo".getBytes()) .build()); - assertThat(this.generatedSnippets.requestParts()).is(tableWithHeader("Part", "Description").row("`b`", "two")); + assertThat(snippets.requestParts()) + .isTable((table) -> table.withHeader("Part", "Description").row("`b`", "two")); } - @Test - public void missingOptionalRequestPart() throws IOException { + @RenderedSnippetTest + void missingOptionalRequestPart(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException { new RequestPartsSnippet( Arrays.asList(partWithName("a").description("one").optional(), partWithName("b").description("two"))) - .document(this.operationBuilder.request("http://localhost").part("b", "bravo".getBytes()).build()); - assertThat(this.generatedSnippets.requestParts()) - .is(tableWithHeader("Part", "Description").row("`a`", "one").row("`b`", "two")); + .document(operationBuilder.request("http://localhost").part("b", "bravo".getBytes()).build()); + assertThat(snippets.requestParts()) + .isTable((table) -> table.withHeader("Part", "Description").row("`a`", "one").row("`b`", "two")); } - @Test - public void presentOptionalRequestPart() throws IOException { + @RenderedSnippetTest + void presentOptionalRequestPart(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException { new RequestPartsSnippet(Arrays.asList(partWithName("a").description("one").optional())) - .document(this.operationBuilder.request("http://localhost").part("a", "one".getBytes()).build()); - assertThat(this.generatedSnippets.requestParts()).is(tableWithHeader("Part", "Description").row("`a`", "one")); + .document(operationBuilder.request("http://localhost").part("a", "one".getBytes()).build()); + assertThat(snippets.requestParts()) + .isTable((table) -> table.withHeader("Part", "Description").row("`a`", "one")); } - @Test - public void requestPartsWithCustomAttributes() throws IOException { - TemplateResourceResolver resolver = mock(TemplateResourceResolver.class); - given(resolver.resolveTemplateResource("request-parts")) - .willReturn(snippetResource("request-parts-with-title")); + @RenderedSnippetTest + @SnippetTemplate(snippet = "request-parts", template = "request-parts-with-title") + void requestPartsWithCustomAttributes(OperationBuilder operationBuilder, AssertableSnippets snippets) + throws IOException { new RequestPartsSnippet( Arrays.asList(partWithName("a").description("one").attributes(key("foo").value("alpha")), partWithName("b").description("two").attributes(key("foo").value("bravo"))), attributes(key("title").value("The title"))) - .document(this.operationBuilder - .attribute(TemplateEngine.class.getName(), new MustacheTemplateEngine(resolver)) - .request("http://localhost") + .document(operationBuilder.request("http://localhost") .part("a", "alpha".getBytes()) .and() .part("b", "bravo".getBytes()) .build()); - assertThat(this.generatedSnippets.requestParts()).contains("The title"); + assertThat(snippets.requestParts()).contains("The title"); } - @Test - public void requestPartsWithCustomDescriptorAttributes() throws IOException { - TemplateResourceResolver resolver = mock(TemplateResourceResolver.class); - given(resolver.resolveTemplateResource("request-parts")) - .willReturn(snippetResource("request-parts-with-extra-column")); + @RenderedSnippetTest + @SnippetTemplate(snippet = "request-parts", template = "request-parts-with-extra-column") + void requestPartsWithCustomDescriptorAttributes(OperationBuilder operationBuilder, AssertableSnippets snippets) + throws IOException { new RequestPartsSnippet( Arrays.asList(partWithName("a").description("one").attributes(key("foo").value("alpha")), partWithName("b").description("two").attributes(key("foo").value("bravo")))) - .document(this.operationBuilder - .attribute(TemplateEngine.class.getName(), new MustacheTemplateEngine(resolver)) - .request("http://localhost") + .document(operationBuilder.request("http://localhost") .part("a", "alpha".getBytes()) .and() .part("b", "bravo".getBytes()) .build()); - assertThat(this.generatedSnippets.requestParts()) - .is(tableWithHeader("Part", "Description", "Foo").row("a", "one", "alpha").row("b", "two", "bravo")); + assertThat(snippets.requestParts()).isTable((table) -> table.withHeader("Part", "Description", "Foo") + .row("a", "one", "alpha") + .row("b", "two", "bravo")); } - @Test - public void requestPartsWithOptionalColumn() throws IOException { - TemplateResourceResolver resolver = mock(TemplateResourceResolver.class); - given(resolver.resolveTemplateResource("request-parts")) - .willReturn(snippetResource("request-parts-with-optional-column")); + @RenderedSnippetTest + @SnippetTemplate(snippet = "request-parts", template = "request-parts-with-optional-column") + void requestPartsWithOptionalColumn(OperationBuilder operationBuilder, AssertableSnippets snippets) + throws IOException { new RequestPartsSnippet( Arrays.asList(partWithName("a").description("one").optional(), partWithName("b").description("two"))) - .document(this.operationBuilder - .attribute(TemplateEngine.class.getName(), new MustacheTemplateEngine(resolver)) - .request("http://localhost") + .document(operationBuilder.request("http://localhost") .part("a", "alpha".getBytes()) .and() .part("b", "bravo".getBytes()) .build()); - assertThat(this.generatedSnippets.requestParts()) - .is(tableWithHeader("Part", "Optional", "Description").row("a", "true", "one").row("b", "false", "two")); + assertThat(snippets.requestParts()).isTable((table) -> table.withHeader("Part", "Optional", "Description") + .row("a", "true", "one") + .row("b", "false", "two")); } - @Test - public void additionalDescriptors() throws IOException { + @RenderedSnippetTest + void additionalDescriptors(OperationBuilder operationBuilder, AssertableSnippets snippets) throws IOException { RequestDocumentation.requestParts(partWithName("a").description("one")) .and(partWithName("b").description("two")) - .document(this.operationBuilder.request("http://localhost") + .document(operationBuilder.request("http://localhost") .part("a", "bravo".getBytes()) .and() .part("b", "bravo".getBytes()) .build()); - assertThat(this.generatedSnippets.requestParts()) - .is(tableWithHeader("Part", "Description").row("`a`", "one").row("`b`", "two")); + assertThat(snippets.requestParts()) + .isTable((table) -> table.withHeader("Part", "Description").row("`a`", "one").row("`b`", "two")); } - @Test - public void requestPartsWithEscapedContent() throws IOException { + @RenderedSnippetTest + void requestPartsWithEscapedContent(OperationBuilder operationBuilder, AssertableSnippets snippets) + throws IOException { RequestDocumentation.requestParts(partWithName("Foo|Bar").description("one|two")) - .document(this.operationBuilder.request("http://localhost").part("Foo|Bar", "baz".getBytes()).build()); - assertThat(this.generatedSnippets.requestParts()).is(tableWithHeader("Part", "Description") - .row(escapeIfNecessary("`Foo|Bar`"), escapeIfNecessary("one|two"))); + .document(operationBuilder.request("http://localhost").part("Foo|Bar", "baz".getBytes()).build()); + assertThat(snippets.requestParts()) + .isTable((table) -> table.withHeader("Part", "Description").row("`Foo|Bar`", "one|two")); + } + + @SnippetTest + void undocumentedPart(OperationBuilder operationBuilder) { + assertThatExceptionOfType(SnippetException.class) + .isThrownBy(() -> new RequestPartsSnippet(Collections.emptyList()) + .document(operationBuilder.request("http://localhost").part("a", "alpha".getBytes()).build())) + .withMessage("Request parts with the following names were not documented: [a]"); + } + + @SnippetTest + void missingPart(OperationBuilder operationBuilder) { + assertThatExceptionOfType(SnippetException.class) + .isThrownBy(() -> new RequestPartsSnippet(Arrays.asList(partWithName("a").description("one"))) + .document(operationBuilder.request("http://localhost").build())) + .withMessage("Request parts with the following names were not found in the request: [a]"); } - private String escapeIfNecessary(String input) { - if (this.templateFormat.getId().equals(TemplateFormats.markdown().getId())) { - return input; - } - return input.replace("|", "\\|"); + @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]"); } } diff --git a/spring-restdocs-core/src/test/java/org/springframework/restdocs/snippet/RestDocumentationContextPlaceholderResolverTests.java b/spring-restdocs-core/src/test/java/org/springframework/restdocs/snippet/RestDocumentationContextPlaceholderResolverTests.java index c2df3049..2993530a 100644 --- a/spring-restdocs-core/src/test/java/org/springframework/restdocs/snippet/RestDocumentationContextPlaceholderResolverTests.java +++ b/spring-restdocs-core/src/test/java/org/springframework/restdocs/snippet/RestDocumentationContextPlaceholderResolverTests.java @@ -16,7 +16,7 @@ package org.springframework.restdocs.snippet; -import org.junit.Test; +import org.junit.jupiter.api.Test; import org.springframework.restdocs.ManualRestDocumentation; import org.springframework.restdocs.RestDocumentationContext; @@ -30,82 +30,82 @@ * @author Andy Wilkinson * */ -public class RestDocumentationContextPlaceholderResolverTests { +class RestDocumentationContextPlaceholderResolverTests { @Test - public void kebabCaseMethodName() { + void kebabCaseMethodName() { assertThat(createResolver("dashSeparatedMethodName").resolvePlaceholder("method-name")) .isEqualTo("dash-separated-method-name"); } @Test - public void kebabCaseMethodNameWithUpperCaseOpeningSection() { + void kebabCaseMethodNameWithUpperCaseOpeningSection() { assertThat(createResolver("URIDashSeparatedMethodName").resolvePlaceholder("method-name")) .isEqualTo("uri-dash-separated-method-name"); } @Test - public void kebabCaseMethodNameWithUpperCaseMidSection() { + void kebabCaseMethodNameWithUpperCaseMidSection() { assertThat(createResolver("dashSeparatedMethodNameWithURIInIt").resolvePlaceholder("method-name")) .isEqualTo("dash-separated-method-name-with-uri-in-it"); } @Test - public void kebabCaseMethodNameWithUpperCaseEndSection() { + void kebabCaseMethodNameWithUpperCaseEndSection() { assertThat(createResolver("dashSeparatedMethodNameWithURI").resolvePlaceholder("method-name")) .isEqualTo("dash-separated-method-name-with-uri"); } @Test - public void snakeCaseMethodName() { + void snakeCaseMethodName() { assertThat(createResolver("underscoreSeparatedMethodName").resolvePlaceholder("method_name")) .isEqualTo("underscore_separated_method_name"); } @Test - public void snakeCaseMethodNameWithUpperCaseOpeningSection() { + void snakeCaseMethodNameWithUpperCaseOpeningSection() { assertThat(createResolver("URIUnderscoreSeparatedMethodName").resolvePlaceholder("method_name")) .isEqualTo("uri_underscore_separated_method_name"); } @Test - public void snakeCaseMethodNameWithUpperCaseMidSection() { + void snakeCaseMethodNameWithUpperCaseMidSection() { assertThat(createResolver("underscoreSeparatedMethodNameWithURIInIt").resolvePlaceholder("method_name")) .isEqualTo("underscore_separated_method_name_with_uri_in_it"); } @Test - public void snakeCaseMethodNameWithUpperCaseEndSection() { + void snakeCaseMethodNameWithUpperCaseEndSection() { assertThat(createResolver("underscoreSeparatedMethodNameWithURI").resolvePlaceholder("method_name")) .isEqualTo("underscore_separated_method_name_with_uri"); } @Test - public void camelCaseMethodName() { + void camelCaseMethodName() { assertThat(createResolver("camelCaseMethodName").resolvePlaceholder("methodName")) .isEqualTo("camelCaseMethodName"); } @Test - public void kebabCaseClassName() { + void kebabCaseClassName() { assertThat(createResolver().resolvePlaceholder("class-name")) .isEqualTo("rest-documentation-context-placeholder-resolver-tests"); } @Test - public void snakeCaseClassName() { + void snakeCaseClassName() { assertThat(createResolver().resolvePlaceholder("class_name")) .isEqualTo("rest_documentation_context_placeholder_resolver_tests"); } @Test - public void camelCaseClassName() { + void camelCaseClassName() { assertThat(createResolver().resolvePlaceholder("ClassName")) .isEqualTo("RestDocumentationContextPlaceholderResolverTests"); } @Test - public void stepCount() { + void stepCount() { assertThat(createResolver("stepCount").resolvePlaceholder("step")).isEqualTo("1"); } diff --git a/spring-restdocs-core/src/test/java/org/springframework/restdocs/snippet/StandardWriterResolverTests.java b/spring-restdocs-core/src/test/java/org/springframework/restdocs/snippet/StandardWriterResolverTests.java index e5c44d43..0ad3cf06 100644 --- a/spring-restdocs-core/src/test/java/org/springframework/restdocs/snippet/StandardWriterResolverTests.java +++ b/spring-restdocs-core/src/test/java/org/springframework/restdocs/snippet/StandardWriterResolverTests.java @@ -21,9 +21,8 @@ import java.io.IOException; import java.io.Writer; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.TemporaryFolder; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; import org.springframework.restdocs.ManualRestDocumentation; import org.springframework.restdocs.RestDocumentationContext; @@ -40,10 +39,10 @@ * * @author Andy Wilkinson */ -public class StandardWriterResolverTests { +class StandardWriterResolverTests { - @Rule - public final TemporaryFolder temp = new TemporaryFolder(); + @TempDir + File temp; private final PlaceholderResolverFactory placeholderResolverFactory = mock(PlaceholderResolverFactory.class); @@ -51,21 +50,21 @@ public class StandardWriterResolverTests { TemplateFormats.asciidoctor()); @Test - public void absoluteInput() { + void absoluteInput() { String absolutePath = new File("foo").getAbsolutePath(); assertThat(this.resolver.resolveFile(absolutePath, "bar.txt", createContext(absolutePath))) .isEqualTo(new File(absolutePath, "bar.txt")); } @Test - public void configuredOutputAndRelativeInput() { + void configuredOutputAndRelativeInput() { File outputDir = new File("foo").getAbsoluteFile(); assertThat(this.resolver.resolveFile("bar", "baz.txt", createContext(outputDir.getAbsolutePath()))) .isEqualTo(new File(outputDir, "bar/baz.txt")); } @Test - public void configuredOutputAndAbsoluteInput() { + void configuredOutputAndAbsoluteInput() { File outputDir = new File("foo").getAbsoluteFile(); String absolutePath = new File("bar").getAbsolutePath(); assertThat(this.resolver.resolveFile(absolutePath, "baz.txt", createContext(outputDir.getAbsolutePath()))) @@ -73,25 +72,27 @@ public void configuredOutputAndAbsoluteInput() { } @Test - public void placeholdersAreResolvedInOperationName() throws IOException { - File outputDirectory = this.temp.newFolder(); + void placeholdersAreResolvedInOperationName() throws IOException { + File outputDirectory = this.temp; RestDocumentationContext context = createContext(outputDirectory.getAbsolutePath()); PlaceholderResolver resolver = mock(PlaceholderResolver.class); given(resolver.resolvePlaceholder("a")).willReturn("alpha"); given(this.placeholderResolverFactory.create(context)).willReturn(resolver); - Writer writer = this.resolver.resolve("{a}", "bravo", context); - assertSnippetLocation(writer, new File(outputDirectory, "alpha/bravo.adoc")); + try (Writer writer = this.resolver.resolve("{a}", "bravo", context)) { + assertSnippetLocation(writer, new File(outputDirectory, "alpha/bravo.adoc")); + } } @Test - public void placeholdersAreResolvedInSnippetName() throws IOException { - File outputDirectory = this.temp.newFolder(); + void placeholdersAreResolvedInSnippetName() throws IOException { + File outputDirectory = this.temp; RestDocumentationContext context = createContext(outputDirectory.getAbsolutePath()); PlaceholderResolver resolver = mock(PlaceholderResolver.class); given(resolver.resolvePlaceholder("b")).willReturn("bravo"); given(this.placeholderResolverFactory.create(context)).willReturn(resolver); - Writer writer = this.resolver.resolve("alpha", "{b}", context); - assertSnippetLocation(writer, new File(outputDirectory, "alpha/bravo.adoc")); + try (Writer writer = this.resolver.resolve("alpha", "{b}", context)) { + assertSnippetLocation(writer, new File(outputDirectory, "alpha/bravo.adoc")); + } } private RestDocumentationContext createContext(String outputDir) { diff --git a/spring-restdocs-core/src/test/java/org/springframework/restdocs/snippet/TemplatedSnippetTests.java b/spring-restdocs-core/src/test/java/org/springframework/restdocs/snippet/TemplatedSnippetTests.java index 7e9cc705..e4c08866 100644 --- a/spring-restdocs-core/src/test/java/org/springframework/restdocs/snippet/TemplatedSnippetTests.java +++ b/spring-restdocs-core/src/test/java/org/springframework/restdocs/snippet/TemplatedSnippetTests.java @@ -21,13 +21,12 @@ import java.util.HashMap; import java.util.Map; -import org.junit.Rule; -import org.junit.Test; +import org.junit.jupiter.api.Test; import org.springframework.restdocs.operation.Operation; -import org.springframework.restdocs.templates.TemplateFormats; -import org.springframework.restdocs.testfixtures.GeneratedSnippets; -import org.springframework.restdocs.testfixtures.OperationBuilder; +import org.springframework.restdocs.testfixtures.jupiter.AssertableSnippets; +import org.springframework.restdocs.testfixtures.jupiter.OperationBuilder; +import org.springframework.restdocs.testfixtures.jupiter.RenderedSnippetTest; import static org.assertj.core.api.Assertions.assertThat; @@ -36,16 +35,10 @@ * * @author Andy Wilkinson */ -public class TemplatedSnippetTests { - - @Rule - public OperationBuilder operationBuilder = new OperationBuilder(TemplateFormats.asciidoctor()); - - @Rule - public GeneratedSnippets snippets = new GeneratedSnippets(TemplateFormats.asciidoctor()); +class TemplatedSnippetTests { @Test - public void attributesAreCopied() { + void attributesAreCopied() { Map attributes = new HashMap<>(); attributes.put("a", "alpha"); TemplatedSnippet snippet = new TestTemplatedSnippet(attributes); @@ -55,22 +48,23 @@ public void attributesAreCopied() { } @Test - public void nullAttributesAreTolerated() { + void nullAttributesAreTolerated() { assertThat(new TestTemplatedSnippet(null).getAttributes()).isNotNull(); assertThat(new TestTemplatedSnippet(null).getAttributes()).isEmpty(); } @Test - public void snippetName() { + void snippetName() { assertThat(new TestTemplatedSnippet(Collections.emptyMap()).getSnippetName()).isEqualTo("test"); } - @Test - public void multipleSnippetsCanBeProducedFromTheSameTemplate() throws IOException { - new TestTemplatedSnippet("one", "multiple-snippets").document(this.operationBuilder.build()); - new TestTemplatedSnippet("two", "multiple-snippets").document(this.operationBuilder.build()); - assertThat(this.snippets.snippet("multiple-snippets-one")).isNotNull(); - assertThat(this.snippets.snippet("multiple-snippets-two")).isNotNull(); + @RenderedSnippetTest + void multipleSnippetsCanBeProducedFromTheSameTemplate(OperationBuilder operationBuilder, AssertableSnippets snippet) + throws IOException { + new TestTemplatedSnippet("one", "multiple-snippets").document(operationBuilder.build()); + new TestTemplatedSnippet("two", "multiple-snippets").document(operationBuilder.build()); + assertThat(snippet.named("multiple-snippets-one")).exists(); + assertThat(snippet.named("multiple-snippets-two")).exists(); } private static class TestTemplatedSnippet extends TemplatedSnippet { diff --git a/spring-restdocs-core/src/test/java/org/springframework/restdocs/templates/StandardTemplateResourceResolverTests.java b/spring-restdocs-core/src/test/java/org/springframework/restdocs/templates/StandardTemplateResourceResolverTests.java index b74dab74..956e9bcb 100644 --- a/spring-restdocs-core/src/test/java/org/springframework/restdocs/templates/StandardTemplateResourceResolverTests.java +++ b/spring-restdocs-core/src/test/java/org/springframework/restdocs/templates/StandardTemplateResourceResolverTests.java @@ -22,7 +22,7 @@ import java.util.Map; import java.util.concurrent.Callable; -import org.junit.Test; +import org.junit.jupiter.api.Test; import org.springframework.core.io.Resource; @@ -34,7 +34,7 @@ * * @author Andy Wilkinson */ -public class StandardTemplateResourceResolverTests { +class StandardTemplateResourceResolverTests { private final TemplateResourceResolver resolver = new StandardTemplateResourceResolver( TemplateFormats.asciidoctor()); @@ -42,7 +42,7 @@ public class StandardTemplateResourceResolverTests { private final TestClassLoader classLoader = new TestClassLoader(); @Test - public void formatSpecificCustomSnippetHasHighestPrecedence() throws IOException { + void formatSpecificCustomSnippetHasHighestPrecedence() throws IOException { this.classLoader.addResource("org/springframework/restdocs/templates/asciidoctor/test.snippet", getClass().getResource("test-format-specific-custom.snippet")); this.classLoader.addResource("org/springframework/restdocs/templates/test.snippet", @@ -62,7 +62,7 @@ public Resource call() { } @Test - public void generalCustomSnippetIsUsedInAbsenceOfFormatSpecificCustomSnippet() throws IOException { + void generalCustomSnippetIsUsedInAbsenceOfFormatSpecificCustomSnippet() throws IOException { this.classLoader.addResource("org/springframework/restdocs/templates/test.snippet", getClass().getResource("test-custom.snippet")); this.classLoader.addResource("org/springframework/restdocs/templates/asciidoctor/default-test.snippet", @@ -80,7 +80,7 @@ public Resource call() { } @Test - public void defaultSnippetIsUsedInAbsenceOfCustomSnippets() throws Exception { + void defaultSnippetIsUsedInAbsenceOfCustomSnippets() throws Exception { this.classLoader.addResource("org/springframework/restdocs/templates/asciidoctor/default-test.snippet", getClass().getResource("test-default.snippet")); Resource snippet = doWithThreadContextClassLoader(this.classLoader, new Callable() { @@ -96,7 +96,7 @@ public Resource call() { } @Test - public void failsIfCustomAndDefaultSnippetsDoNotExist() { + void failsIfCustomAndDefaultSnippetsDoNotExist() { assertThatIllegalStateException() .isThrownBy(() -> doWithThreadContextClassLoader(this.classLoader, () -> StandardTemplateResourceResolverTests.this.resolver.resolveTemplateResource("test"))) diff --git a/spring-restdocs-core/src/test/java/org/springframework/restdocs/templates/mustache/AsciidoctorTableCellContentLambdaTests.java b/spring-restdocs-core/src/test/java/org/springframework/restdocs/templates/mustache/AsciidoctorTableCellContentLambdaTests.java index f465e233..c1d03350 100644 --- a/spring-restdocs-core/src/test/java/org/springframework/restdocs/templates/mustache/AsciidoctorTableCellContentLambdaTests.java +++ b/spring-restdocs-core/src/test/java/org/springframework/restdocs/templates/mustache/AsciidoctorTableCellContentLambdaTests.java @@ -19,7 +19,7 @@ import java.io.IOException; import java.io.StringWriter; -import org.junit.Test; +import org.junit.jupiter.api.Test; import org.springframework.restdocs.mustache.Template.Fragment; @@ -32,10 +32,10 @@ * * @author Andy Wilkinson */ -public class AsciidoctorTableCellContentLambdaTests { +class AsciidoctorTableCellContentLambdaTests { @Test - public void verticalBarCharactersAreEscaped() throws IOException { + void verticalBarCharactersAreEscaped() throws IOException { Fragment fragment = mock(Fragment.class); given(fragment.execute()).willReturn("|foo|bar|baz|"); StringWriter writer = new StringWriter(); @@ -44,7 +44,7 @@ public void verticalBarCharactersAreEscaped() throws IOException { } @Test - public void escapedVerticalBarCharactersAreNotEscapedAgain() throws IOException { + void escapedVerticalBarCharactersAreNotEscapedAgain() throws IOException { Fragment fragment = mock(Fragment.class); given(fragment.execute()).willReturn("\\|foo|bar\\|baz|"); StringWriter writer = new StringWriter(); diff --git a/spring-restdocs-core/src/test/resources/custom-snippet-templates/markdown/request-fields-with-title.snippet b/spring-restdocs-core/src/test/resources/custom-snippet-templates/markdown/request-fields-with-title.snippet index 24bb63fa..ff141ad3 100644 --- a/spring-restdocs-core/src/test/resources/custom-snippet-templates/markdown/request-fields-with-title.snippet +++ b/spring-restdocs-core/src/test/resources/custom-snippet-templates/markdown/request-fields-with-title.snippet @@ -1,4 +1,5 @@ {{title}} + Path | Type | Description ---- | ---- | ----------- {{#fields}} diff --git a/spring-restdocs-core/src/testFixtures/java/org/springframework/restdocs/testfixtures/GeneratedSnippets.java b/spring-restdocs-core/src/testFixtures/java/org/springframework/restdocs/testfixtures/GeneratedSnippets.java deleted file mode 100644 index 2131823d..00000000 --- a/spring-restdocs-core/src/testFixtures/java/org/springframework/restdocs/testfixtures/GeneratedSnippets.java +++ /dev/null @@ -1,144 +0,0 @@ -/* - * Copyright 2014-present 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()); - } - -} diff --git a/spring-restdocs-core/src/testFixtures/java/org/springframework/restdocs/testfixtures/OperationTestRule.java b/spring-restdocs-core/src/testFixtures/java/org/springframework/restdocs/testfixtures/OperationTestRule.java deleted file mode 100644 index 4599992a..00000000 --- a/spring-restdocs-core/src/testFixtures/java/org/springframework/restdocs/testfixtures/OperationTestRule.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright 2014-present 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); - -} diff --git a/spring-restdocs-core/src/testFixtures/java/org/springframework/restdocs/testfixtures/OutputCaptureRule.java b/spring-restdocs-core/src/testFixtures/java/org/springframework/restdocs/testfixtures/OutputCaptureRule.java deleted file mode 100644 index be17f612..00000000 --- a/spring-restdocs-core/src/testFixtures/java/org/springframework/restdocs/testfixtures/OutputCaptureRule.java +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Copyright 2014-present 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. - *

- * To use add as a {@link Rule @Rule}: - * - *

- * public class MyTest {
- *
- *     @Rule
- *     public OutputCaptureRule output = new OutputCaptureRule();
- *
- *     @Test
- *     public void test() {
- *         assertThat(output).contains("ok");
- *     }
- *
- * }
- * 
- * - * @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(); - } - -} diff --git a/spring-restdocs-core/src/testFixtures/java/org/springframework/restdocs/testfixtures/jupiter/AssertableSnippets.java b/spring-restdocs-core/src/testFixtures/java/org/springframework/restdocs/testfixtures/jupiter/AssertableSnippets.java new file mode 100644 index 00000000..94b937b1 --- /dev/null +++ b/spring-restdocs-core/src/testFixtures/java/org/springframework/restdocs/testfixtures/jupiter/AssertableSnippets.java @@ -0,0 +1,679 @@ +/* + * Copyright 2014-present 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 { + + 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 { + + private TableSnippetAssert(String actual) { + super(actual, TableSnippetAssert.class); + } + + public void isTable(UnaryOperator> 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> 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 { + + @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 { + + @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 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 { + + 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 { + + private CodeBlockSnippetAssert(String actual) { + super(actual, CodeBlockSnippetAssert.class); + } + + public void isCodeBlock(UnaryOperator> 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> 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 { + + @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 { + + @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 { + + 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 { + + private HttpRequestSnippetAssert(String actual) { + super(actual, HttpRequestSnippetAssert.class); + } + + public void isHttpRequest(UnaryOperator> 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> 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 { + + 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 { + + 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 { + + 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 { + + private HttpResponseSnippetAssert(String actual) { + super(actual, HttpResponseSnippetAssert.class); + } + + public void isHttpResponse(UnaryOperator> 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> 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 { + + 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 { + + 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 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 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(); + } + + } + +} diff --git a/spring-restdocs-core/src/testFixtures/java/org/springframework/restdocs/testfixtures/CapturedOutput.java b/spring-restdocs-core/src/testFixtures/java/org/springframework/restdocs/testfixtures/jupiter/CapturedOutput.java similarity index 79% rename from spring-restdocs-core/src/testFixtures/java/org/springframework/restdocs/testfixtures/CapturedOutput.java rename to spring-restdocs-core/src/testFixtures/java/org/springframework/restdocs/testfixtures/jupiter/CapturedOutput.java index a325476c..7ead966b 100644 --- a/spring-restdocs-core/src/testFixtures/java/org/springframework/restdocs/testfixtures/CapturedOutput.java +++ b/spring-restdocs-core/src/testFixtures/java/org/springframework/restdocs/testfixtures/jupiter/CapturedOutput.java @@ -14,16 +14,11 @@ * limitations under the License. */ -package org.springframework.restdocs.testfixtures; +package org.springframework.restdocs.testfixtures.jupiter; /** * Provides access to {@link System#out System.out} and {@link System#err System.err} - * output that has been captured by the {@link OutputCaptureRule}. Can be used to apply - * assertions using AssertJ. For example:
- * 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
- * 
+ * output that has been captured. * * @author Madhura Bhave * @author Phillip Webb diff --git a/spring-restdocs-core/src/testFixtures/java/org/springframework/restdocs/testfixtures/OperationBuilder.java b/spring-restdocs-core/src/testFixtures/java/org/springframework/restdocs/testfixtures/jupiter/OperationBuilder.java similarity index 93% rename from spring-restdocs-core/src/testFixtures/java/org/springframework/restdocs/testfixtures/OperationBuilder.java rename to spring-restdocs-core/src/testFixtures/java/org/springframework/restdocs/testfixtures/jupiter/OperationBuilder.java index a3f12ec5..ac796d50 100644 --- a/spring-restdocs-core/src/testFixtures/java/org/springframework/restdocs/testfixtures/OperationBuilder.java +++ b/spring-restdocs-core/src/testFixtures/java/org/springframework/restdocs/testfixtures/jupiter/OperationBuilder.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.restdocs.testfixtures; +package org.springframework.restdocs.testfixtures.jupiter; import java.io.File; import java.net.URI; @@ -26,8 +26,6 @@ import java.util.Map; import java.util.Set; -import org.junit.runners.model.Statement; - import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.HttpStatus; @@ -59,21 +57,27 @@ * * @author Andy Wilkinson */ -public class OperationBuilder extends OperationTestRule { +public class OperationBuilder { private final Map attributes = new HashMap<>(); - private OperationResponseBuilder responseBuilder; - - private String name; + private final File outputDirectory; - private File outputDirectory; + private final String name; private final TemplateFormat templateFormat; + private OperationResponseBuilder responseBuilder; + private OperationRequestBuilder requestBuilder; - public OperationBuilder(TemplateFormat templateFormat) { + OperationBuilder(File outputDirectory, String name) { + this(outputDirectory, name, null); + } + + OperationBuilder(File outputDirectory, String name, TemplateFormat templateFormat) { + this.outputDirectory = outputDirectory; + this.name = name; this.templateFormat = templateFormat; } @@ -92,13 +96,6 @@ public OperationBuilder attribute(String name, Object value) { return this; } - private void prepare(String operationName, File outputDirectory) { - this.name = operationName; - this.outputDirectory = outputDirectory; - this.requestBuilder = null; - this.attributes.clear(); - } - public Operation build() { if (this.attributes.get(TemplateEngine.class.getName()) == null) { Map templateContext = new HashMap<>(); @@ -127,12 +124,6 @@ private RestDocumentationContext createContext() { 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}. */ diff --git a/spring-restdocs-core/src/testFixtures/java/org/springframework/restdocs/testfixtures/OutputCapture.java b/spring-restdocs-core/src/testFixtures/java/org/springframework/restdocs/testfixtures/jupiter/OutputCapture.java similarity index 95% rename from spring-restdocs-core/src/testFixtures/java/org/springframework/restdocs/testfixtures/OutputCapture.java rename to spring-restdocs-core/src/testFixtures/java/org/springframework/restdocs/testfixtures/jupiter/OutputCapture.java index 25674c8d..6d56a339 100644 --- a/spring-restdocs-core/src/testFixtures/java/org/springframework/restdocs/testfixtures/OutputCapture.java +++ b/spring-restdocs-core/src/testFixtures/java/org/springframework/restdocs/testfixtures/jupiter/OutputCapture.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.restdocs.testfixtures; +package org.springframework.restdocs.testfixtures.jupiter; import java.io.IOException; import java.io.OutputStream; @@ -35,8 +35,6 @@ * @author Madhura Bhave * @author Phillip Webb * @author Andy Wilkinson - * @author Sam Brannen - * @see OutputCaptureRule */ class OutputCapture implements CapturedOutput { @@ -61,7 +59,7 @@ public boolean equals(Object obj) { if (obj == this) { return true; } - if (obj instanceof CapturedOutput || obj instanceof CharSequence) { + if (obj instanceof CharSequence) { return getAll().equals(obj.toString()); } return false; @@ -123,17 +121,17 @@ private String get(Predicate filter) { } /** - * A capture session that captures {@link System#out System.out} and {@link System#out + * A capture session that captures {@link System#out System.out} and {@link System#err * System.err}. */ private static class SystemCapture { - private final Object monitor = new Object(); - private final PrintStreamCapture out; private final PrintStreamCapture err; + private final Object monitor = new Object(); + private final List capturedStrings = new ArrayList<>(); SystemCapture() { @@ -195,8 +193,8 @@ PrintStream getParent() { } private static PrintStream getSystemStream(PrintStream printStream) { - while (printStream instanceof PrintStreamCapture) { - printStream = ((PrintStreamCapture) printStream).getParent(); + while (printStream instanceof PrintStreamCapture printStreamCapture) { + printStream = printStreamCapture.getParent(); } return printStream; } diff --git a/spring-restdocs-core/src/testFixtures/java/org/springframework/restdocs/testfixtures/jupiter/OutputCaptureExtension.java b/spring-restdocs-core/src/testFixtures/java/org/springframework/restdocs/testfixtures/jupiter/OutputCaptureExtension.java new file mode 100644 index 00000000..8d3ff491 --- /dev/null +++ b/spring-restdocs-core/src/testFixtures/java/org/springframework/restdocs/testfixtures/jupiter/OutputCaptureExtension.java @@ -0,0 +1,112 @@ +/* + * Copyright 2014-present 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. + *

+ * To use with {@link ExtendWith @ExtendWith}, inject the {@link CapturedOutput} as an + * argument to your test class constructor, test method, or lifecycle methods: + * + *

+ * @ExtendWith(OutputCaptureExtension.class)
+ * class MyTest {
+ *
+ *     @Test
+ *     void test(CapturedOutput output) {
+ *         System.out.println("ok");
+ *         assertThat(output).contains("ok");
+ *         System.err.println("error");
+ *     }
+ *
+ *     @AfterEach
+ *     void after(CapturedOutput output) {
+ *         assertThat(output.getOut()).contains("ok");
+ *         assertThat(output.getErr()).contains("error");
+ *     }
+ *
+ * }
+ * 
+ * + * @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())); + } + +} diff --git a/spring-restdocs-core/src/testFixtures/java/org/springframework/restdocs/testfixtures/jupiter/RenderedSnippetTest.java b/spring-restdocs-core/src/testFixtures/java/org/springframework/restdocs/testfixtures/jupiter/RenderedSnippetTest.java new file mode 100644 index 00000000..aef76d59 --- /dev/null +++ b/spring-restdocs-core/src/testFixtures/java/org/springframework/restdocs/testfixtures/jupiter/RenderedSnippetTest.java @@ -0,0 +1,78 @@ +/* + * Copyright 2014-present 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). + *

+ * A rendered snippet test method can inject the following types: + *

    + *
  • {@link OperationBuilder}
  • + *
  • {@link AssertableSnippets}
  • + *
+ * + * @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; + } + + } + +} diff --git a/spring-restdocs-core/src/testFixtures/java/org/springframework/restdocs/testfixtures/jupiter/RenderedSnippetTestExtension.java b/spring-restdocs-core/src/testFixtures/java/org/springframework/restdocs/testfixtures/jupiter/RenderedSnippetTestExtension.java new file mode 100644 index 00000000..1d25c45d --- /dev/null +++ b/spring-restdocs-core/src/testFixtures/java/org/springframework/restdocs/testfixtures/jupiter/RenderedSnippetTestExtension.java @@ -0,0 +1,155 @@ +/* + * Copyright 2014-present 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 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 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"); + } + + } + +} diff --git a/spring-restdocs-core/src/testFixtures/java/org/springframework/restdocs/testfixtures/jupiter/SnippetTemplate.java b/spring-restdocs-core/src/testFixtures/java/org/springframework/restdocs/testfixtures/jupiter/SnippetTemplate.java new file mode 100644 index 00000000..50f50842 --- /dev/null +++ b/spring-restdocs-core/src/testFixtures/java/org/springframework/restdocs/testfixtures/jupiter/SnippetTemplate.java @@ -0,0 +1,46 @@ +/* + * Copyright 2014-present 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(); + +} diff --git a/spring-restdocs-core/src/testFixtures/java/org/springframework/restdocs/testfixtures/jupiter/SnippetTest.java b/spring-restdocs-core/src/testFixtures/java/org/springframework/restdocs/testfixtures/jupiter/SnippetTest.java new file mode 100644 index 00000000..1fa1fdb7 --- /dev/null +++ b/spring-restdocs-core/src/testFixtures/java/org/springframework/restdocs/testfixtures/jupiter/SnippetTest.java @@ -0,0 +1,47 @@ +/* + * Copyright 2014-present 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}. + *

+ * A snippet test method can inject the following types: + *

    + *
  • {@link OperationBuilder}
  • + *
+ * + * @author Andy Wilkinson + */ +@Test +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +@ExtendWith(SnippetTestExtension.class) +public @interface SnippetTest { + +} diff --git a/spring-restdocs-core/src/testFixtures/java/org/springframework/restdocs/testfixtures/jupiter/SnippetTestExtension.java b/spring-restdocs-core/src/testFixtures/java/org/springframework/restdocs/testfixtures/jupiter/SnippetTestExtension.java new file mode 100644 index 00000000..b8669fca --- /dev/null +++ b/spring-restdocs-core/src/testFixtures/java/org/springframework/restdocs/testfixtures/jupiter/SnippetTestExtension.java @@ -0,0 +1,66 @@ +/* + * Copyright 2014-present 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; + } + +} diff --git a/spring-restdocs-mockmvc/build.gradle b/spring-restdocs-mockmvc/build.gradle index a1cc2111..e7780e37 100644 --- a/spring-restdocs-mockmvc/build.gradle +++ b/spring-restdocs-mockmvc/build.gradle @@ -1,8 +1,8 @@ plugins { - id "io.spring.compatibility-test" version "0.0.4" + id 'org.springframework.restdocs.conventions' id "java-library" id "maven-publish" - id "optional-dependencies" + id "org.springframework.restdocs.optional-dependencies" } description = "Spring REST Docs MockMvc" @@ -14,18 +14,5 @@ dependencies { implementation("jakarta.servlet:jakarta.servlet-api") - internal(platform(project(":spring-restdocs-platform"))) - testImplementation(testFixtures(project(":spring-restdocs-core"))) - testImplementation("junit:junit") - testImplementation("org.assertj:assertj-core") - testImplementation("org.hamcrest:hamcrest-library") - testImplementation("org.mockito:mockito-core") } - -compatibilityTest { - dependency("Spring Framework") { springFramework -> - springFramework.groupId = "org.springframework" - springFramework.versions = ["6.0.+", "6.1.+"] - } -} \ No newline at end of file diff --git a/spring-restdocs-mockmvc/src/main/java/org/springframework/restdocs/mockmvc/MockMvcRequestConverter.java b/spring-restdocs-mockmvc/src/main/java/org/springframework/restdocs/mockmvc/MockMvcRequestConverter.java index dd0c03c2..e8ddd1e8 100644 --- a/spring-restdocs-mockmvc/src/main/java/org/springframework/restdocs/mockmvc/MockMvcRequestConverter.java +++ b/spring-restdocs-mockmvc/src/main/java/org/springframework/restdocs/mockmvc/MockMvcRequestConverter.java @@ -32,6 +32,7 @@ import jakarta.servlet.ServletException; import jakarta.servlet.http.Part; +import org.jspecify.annotations.Nullable; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; @@ -66,9 +67,12 @@ public OperationRequest convert(MockHttpServletRequest mockRequest) { HttpHeaders headers = extractHeaders(mockRequest); List parts = extractParts(mockRequest); Collection cookies = extractCookies(mockRequest, headers); - return new OperationRequestFactory().create(getRequestUri(mockRequest), - HttpMethod.valueOf(mockRequest.getMethod()), getRequestContent(mockRequest, headers), headers, - parts, cookies); + String requestMethod = mockRequest.getMethod(); + if (requestMethod == null) { + throw new IllegalStateException("MockHttpServletRequest cannot be converted as its method is null"); + } + return new OperationRequestFactory().create(getRequestUri(mockRequest), HttpMethod.valueOf(requestMethod), + getRequestContent(mockRequest, headers), headers, parts, cookies); } catch (Exception ex) { throw new ConversionException(ex); @@ -96,7 +100,7 @@ private String urlEncodedParameters(MockHttpServletRequest mockRequest) { for (String name : IterableEnumeration.of(mockRequest.getParameterNames())) { if (!queryParameters.containsKey(name)) { String[] values = mockRequest.getParameterValues(name); - if (values.length == 0) { + if (values == null || values.length == 0) { append(parameters, name); } else { @@ -109,7 +113,7 @@ private String urlEncodedParameters(MockHttpServletRequest mockRequest) { return parameters.toString(); } - private byte[] getRequestContent(MockHttpServletRequest mockRequest, HttpHeaders headers) { + private byte @Nullable [] getRequestContent(MockHttpServletRequest mockRequest, HttpHeaders headers) { byte[] content = mockRequest.getContentAsByteArray(); if ("GET".equals(mockRequest.getMethod())) { return content; @@ -247,7 +251,7 @@ private static String urlEncode(String s) { return URLEncoder.encode(s, StandardCharsets.UTF_8); } - private static MultiValueMap parse(String query) { + private static MultiValueMap parse(@Nullable String query) { MultiValueMap parameters = new LinkedMultiValueMap<>(); if (!StringUtils.hasLength(query)) { return parameters; diff --git a/spring-restdocs-mockmvc/src/main/java/org/springframework/restdocs/mockmvc/MockMvcResponseConverter.java b/spring-restdocs-mockmvc/src/main/java/org/springframework/restdocs/mockmvc/MockMvcResponseConverter.java index 7c6dd3e4..1f6fa8c2 100644 --- a/spring-restdocs-mockmvc/src/main/java/org/springframework/restdocs/mockmvc/MockMvcResponseConverter.java +++ b/spring-restdocs-mockmvc/src/main/java/org/springframework/restdocs/mockmvc/MockMvcResponseConverter.java @@ -68,7 +68,7 @@ private HttpHeaders extractHeaders(MockHttpServletResponse response) { } } - if (response.getCookies() != null && !headers.containsKey(HttpHeaders.SET_COOKIE)) { + if (response.getCookies() != null && !headers.containsHeader(HttpHeaders.SET_COOKIE)) { for (Cookie cookie : response.getCookies()) { headers.add(HttpHeaders.SET_COOKIE, generateSetCookieHeader(cookie)); } diff --git a/spring-restdocs-mockmvc/src/main/java/org/springframework/restdocs/mockmvc/MockMvcRestDocumentationConfigurer.java b/spring-restdocs-mockmvc/src/main/java/org/springframework/restdocs/mockmvc/MockMvcRestDocumentationConfigurer.java index 9ab42e9c..66b45cd9 100644 --- a/spring-restdocs-mockmvc/src/main/java/org/springframework/restdocs/mockmvc/MockMvcRestDocumentationConfigurer.java +++ b/spring-restdocs-mockmvc/src/main/java/org/springframework/restdocs/mockmvc/MockMvcRestDocumentationConfigurer.java @@ -21,6 +21,8 @@ import java.util.Map; import java.util.function.Function; +import org.jspecify.annotations.Nullable; + import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.restdocs.RestDocumentationContext; import org.springframework.restdocs.RestDocumentationContextProvider; @@ -88,12 +90,12 @@ public MockMvcOperationPreprocessorsConfigurer operationPreprocessors() { private final class ConfigurerApplyingRequestPostProcessor implements RequestPostProcessor { - private static final Function urlTemplateExtractor; + private static final Function urlTemplateExtractor; static { - Function fromRequestAttribute = ( + Function fromRequestAttribute = ( request) -> (String) request.getAttribute(RestDocumentationGenerator.ATTRIBUTE_NAME_URL_TEMPLATE); - Function extractor; + Function extractor; try { Method accessorMethod = MockHttpServletRequest.class.getMethod("getUriTemplate"); extractor = (request) -> { diff --git a/spring-restdocs-mockmvc/src/main/java/org/springframework/restdocs/mockmvc/UriConfigurer.java b/spring-restdocs-mockmvc/src/main/java/org/springframework/restdocs/mockmvc/UriConfigurer.java index a9ad8fcb..b396ba30 100644 --- a/spring-restdocs-mockmvc/src/main/java/org/springframework/restdocs/mockmvc/UriConfigurer.java +++ b/spring-restdocs-mockmvc/src/main/java/org/springframework/restdocs/mockmvc/UriConfigurer.java @@ -102,6 +102,9 @@ public UriConfigurer withPort(int port) { public void apply(Map configuration, RestDocumentationContext context) { MockHttpServletRequest request = (MockHttpServletRequest) configuration .get(MockHttpServletRequest.class.getName()); + if (request == null) { + throw new IllegalStateException("MockHttpServletRequest not found in configuration"); + } request.setScheme(this.scheme); request.setServerPort(this.port); request.setServerName(this.host); diff --git a/spring-restdocs-mockmvc/src/main/java/org/springframework/restdocs/mockmvc/package-info.java b/spring-restdocs-mockmvc/src/main/java/org/springframework/restdocs/mockmvc/package-info.java index b4822aa0..02497078 100644 --- a/spring-restdocs-mockmvc/src/main/java/org/springframework/restdocs/mockmvc/package-info.java +++ b/spring-restdocs-mockmvc/src/main/java/org/springframework/restdocs/mockmvc/package-info.java @@ -17,4 +17,7 @@ /** * Core classes for using Spring REST Docs with Spring Test's MockMvc. */ +@NullMarked package org.springframework.restdocs.mockmvc; + +import org.jspecify.annotations.NullMarked; diff --git a/spring-restdocs-mockmvc/src/test/java/org/springframework/restdocs/mockmvc/MockMvcRequestConverterTests.java b/spring-restdocs-mockmvc/src/test/java/org/springframework/restdocs/mockmvc/MockMvcRequestConverterTests.java index 66af0e11..27a08b9c 100644 --- a/spring-restdocs-mockmvc/src/test/java/org/springframework/restdocs/mockmvc/MockMvcRequestConverterTests.java +++ b/spring-restdocs-mockmvc/src/test/java/org/springframework/restdocs/mockmvc/MockMvcRequestConverterTests.java @@ -23,7 +23,7 @@ import java.util.Iterator; import jakarta.servlet.http.Part; -import org.junit.Test; +import org.junit.jupiter.api.Test; import org.springframework.http.HttpMethod; import org.springframework.http.MediaType; @@ -37,6 +37,7 @@ import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.entry; import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.mock; @@ -45,19 +46,19 @@ * * @author Andy Wilkinson */ -public class MockMvcRequestConverterTests { +class MockMvcRequestConverterTests { private final MockMvcRequestConverter factory = new MockMvcRequestConverter(); @Test - public void httpRequest() { + void httpRequest() { OperationRequest request = createOperationRequest(MockMvcRequestBuilders.get("/foo")); assertThat(request.getUri()).isEqualTo(URI.create("http://localhost/foo")); assertThat(request.getMethod()).isEqualTo(HttpMethod.GET); } @Test - public void httpRequestWithCustomPort() { + void httpRequestWithCustomPort() { MockHttpServletRequest mockRequest = MockMvcRequestBuilders.get("/foo").buildRequest(new MockServletContext()); mockRequest.setServerPort(8080); OperationRequest request = this.factory.convert(mockRequest); @@ -66,24 +67,24 @@ public void httpRequestWithCustomPort() { } @Test - public void requestWithContextPath() { + void requestWithContextPath() { OperationRequest request = createOperationRequest(MockMvcRequestBuilders.get("/foo/bar").contextPath("/foo")); assertThat(request.getUri()).isEqualTo(URI.create("http://localhost/foo/bar")); assertThat(request.getMethod()).isEqualTo(HttpMethod.GET); } @Test - public void requestWithHeaders() { + void requestWithHeaders() { OperationRequest request = createOperationRequest( MockMvcRequestBuilders.get("/foo").header("a", "alpha", "apple").header("b", "bravo")); assertThat(request.getUri()).isEqualTo(URI.create("http://localhost/foo")); assertThat(request.getMethod()).isEqualTo(HttpMethod.GET); - assertThat(request.getHeaders()).containsEntry("a", Arrays.asList("alpha", "apple")); - assertThat(request.getHeaders()).containsEntry("b", Arrays.asList("bravo")); + assertThat(request.getHeaders().headerSet()).contains(entry("a", Arrays.asList("alpha", "apple")), + entry("b", Arrays.asList("bravo"))); } @Test - public void requestWithCookies() { + void requestWithCookies() { OperationRequest request = createOperationRequest(MockMvcRequestBuilders.get("/foo") .cookie(new jakarta.servlet.http.Cookie("cookieName1", "cookieVal1"), new jakarta.servlet.http.Cookie("cookieName2", "cookieVal2"))); @@ -103,7 +104,7 @@ public void requestWithCookies() { } @Test - public void httpsRequest() { + void httpsRequest() { MockHttpServletRequest mockRequest = MockMvcRequestBuilders.get("/foo").buildRequest(new MockServletContext()); mockRequest.setScheme("https"); mockRequest.setServerPort(443); @@ -113,7 +114,7 @@ public void httpsRequest() { } @Test - public void httpsRequestWithCustomPort() { + void httpsRequestWithCustomPort() { MockHttpServletRequest mockRequest = MockMvcRequestBuilders.get("/foo").buildRequest(new MockServletContext()); mockRequest.setScheme("https"); mockRequest.setServerPort(8443); @@ -123,7 +124,7 @@ public void httpsRequestWithCustomPort() { } @Test - public void getRequestWithParametersProducesUriWithQueryString() { + void getRequestWithParametersProducesUriWithQueryString() { OperationRequest request = createOperationRequest( MockMvcRequestBuilders.get("/foo").param("a", "alpha", "apple").param("b", "br&vo")); assertThat(request.getUri()).isEqualTo(URI.create("http://localhost/foo?a=alpha&a=apple&b=br%26vo")); @@ -131,7 +132,7 @@ public void getRequestWithParametersProducesUriWithQueryString() { } @Test - public void getRequestWithQueryString() { + void getRequestWithQueryString() { MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.get("/foo?a=alpha&b=bravo"); OperationRequest request = createOperationRequest(builder); assertThat(request.getUri()).isEqualTo(URI.create("http://localhost/foo?a=alpha&b=bravo")); @@ -139,7 +140,7 @@ public void getRequestWithQueryString() { } @Test - public void postRequestWithParametersCreatesFormUrlEncodedContent() { + void postRequestWithParametersCreatesFormUrlEncodedContent() { OperationRequest request = createOperationRequest( MockMvcRequestBuilders.post("/foo").param("a", "alpha", "apple").param("b", "br&vo")); assertThat(request.getUri()).isEqualTo(URI.create("http://localhost/foo")); @@ -149,7 +150,7 @@ public void postRequestWithParametersCreatesFormUrlEncodedContent() { } @Test - public void postRequestWithParametersAndQueryStringCreatesFormUrlEncodedContentWithoutDuplication() { + void postRequestWithParametersAndQueryStringCreatesFormUrlEncodedContentWithoutDuplication() { OperationRequest request = createOperationRequest( MockMvcRequestBuilders.post("/foo?a=alpha").param("a", "apple").param("b", "br&vo")); assertThat(request.getUri()).isEqualTo(URI.create("http://localhost/foo?a=alpha")); @@ -159,7 +160,7 @@ public void postRequestWithParametersAndQueryStringCreatesFormUrlEncodedContentW } @Test - public void mockMultipartFileUpload() { + void mockMultipartFileUpload() { OperationRequest request = createOperationRequest(MockMvcRequestBuilders.multipart("/foo") .file(new MockMultipartFile("file", new byte[] { 1, 2, 3, 4 }))); assertThat(request.getUri()).isEqualTo(URI.create("http://localhost/foo")); @@ -174,7 +175,7 @@ public void mockMultipartFileUpload() { } @Test - public void mockMultipartFileUploadWithContentType() { + void mockMultipartFileUploadWithContentType() { OperationRequest request = createOperationRequest(MockMvcRequestBuilders.multipart("/foo") .file(new MockMultipartFile("file", "original", "image/png", new byte[] { 1, 2, 3, 4 }))); assertThat(request.getUri()).isEqualTo(URI.create("http://localhost/foo")); @@ -188,7 +189,7 @@ public void mockMultipartFileUploadWithContentType() { } @Test - public void requestWithPart() throws IOException { + void requestWithPart() throws IOException { MockHttpServletRequest mockRequest = MockMvcRequestBuilders.get("/foo").buildRequest(new MockServletContext()); Part mockPart = mock(Part.class); given(mockPart.getHeaderNames()).willReturn(Arrays.asList("a", "b")); @@ -210,7 +211,7 @@ public void requestWithPart() throws IOException { } @Test - public void requestWithPartWithContentType() throws IOException { + void requestWithPartWithContentType() throws IOException { MockHttpServletRequest mockRequest = MockMvcRequestBuilders.get("/foo").buildRequest(new MockServletContext()); Part mockPart = mock(Part.class); given(mockPart.getHeaderNames()).willReturn(Arrays.asList("a", "b")); diff --git a/spring-restdocs-mockmvc/src/test/java/org/springframework/restdocs/mockmvc/MockMvcResponseConverterTests.java b/spring-restdocs-mockmvc/src/test/java/org/springframework/restdocs/mockmvc/MockMvcResponseConverterTests.java index 78a947e9..61ca60ef 100644 --- a/spring-restdocs-mockmvc/src/test/java/org/springframework/restdocs/mockmvc/MockMvcResponseConverterTests.java +++ b/spring-restdocs-mockmvc/src/test/java/org/springframework/restdocs/mockmvc/MockMvcResponseConverterTests.java @@ -20,7 +20,7 @@ import jakarta.servlet.http.Cookie; import jakarta.servlet.http.HttpServletResponse; -import org.junit.Test; +import org.junit.jupiter.api.Test; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; @@ -30,18 +30,19 @@ import org.springframework.restdocs.operation.ResponseCookie; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.entry; /** * Tests for {@link MockMvcResponseConverter}. * * @author Tomasz Kopczynski */ -public class MockMvcResponseConverterTests { +class MockMvcResponseConverterTests { private final MockMvcResponseConverter factory = new MockMvcResponseConverter(); @Test - public void basicResponse() { + void basicResponse() { MockHttpServletResponse response = new MockHttpServletResponse(); response.setStatus(HttpServletResponse.SC_OK); OperationResponse operationResponse = this.factory.convert(response); @@ -49,7 +50,7 @@ public void basicResponse() { } @Test - public void responseWithCookie() { + void responseWithCookie() { MockHttpServletResponse response = new MockHttpServletResponse(); response.setStatus(HttpServletResponse.SC_OK); Cookie cookie = new Cookie("name", "value"); @@ -57,16 +58,15 @@ public void responseWithCookie() { cookie.setHttpOnly(true); response.addCookie(cookie); OperationResponse operationResponse = this.factory.convert(response); - assertThat(operationResponse.getHeaders()).hasSize(1); - assertThat(operationResponse.getHeaders()).containsEntry(HttpHeaders.SET_COOKIE, - Collections.singletonList("name=value; Domain=localhost; HttpOnly")); + assertThat(operationResponse.getHeaders().headerSet()).containsOnly( + entry(HttpHeaders.SET_COOKIE, Collections.singletonList("name=value; Domain=localhost; HttpOnly"))); assertThat(operationResponse.getCookies()).hasSize(1); assertThat(operationResponse.getCookies()).first().extracting(ResponseCookie::getName).isEqualTo("name"); assertThat(operationResponse.getCookies()).first().extracting(ResponseCookie::getValue).isEqualTo("value"); } @Test - public void responseWithCustomStatus() { + void responseWithCustomStatus() { MockHttpServletResponse response = new MockHttpServletResponse(); response.setStatus(600); OperationResponse operationResponse = this.factory.convert(response); diff --git a/spring-restdocs-mockmvc/src/test/java/org/springframework/restdocs/mockmvc/MockMvcRestDocumentationConfigurerTests.java b/spring-restdocs-mockmvc/src/test/java/org/springframework/restdocs/mockmvc/MockMvcRestDocumentationConfigurerTests.java index e6ba6aa9..3a46e8df 100644 --- a/spring-restdocs-mockmvc/src/test/java/org/springframework/restdocs/mockmvc/MockMvcRestDocumentationConfigurerTests.java +++ b/spring-restdocs-mockmvc/src/test/java/org/springframework/restdocs/mockmvc/MockMvcRestDocumentationConfigurerTests.java @@ -19,12 +19,13 @@ import java.lang.reflect.Method; import java.util.Map; -import org.junit.Assume; -import org.junit.Rule; -import org.junit.Test; +import org.junit.jupiter.api.Assumptions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.mock.web.MockHttpServletRequest; -import org.springframework.restdocs.JUnitRestDocumentation; +import org.springframework.restdocs.RestDocumentationContextProvider; +import org.springframework.restdocs.RestDocumentationExtension; import org.springframework.restdocs.generate.RestDocumentationGenerator; import org.springframework.test.web.servlet.request.RequestPostProcessor; import org.springframework.util.ReflectionUtils; @@ -41,24 +42,22 @@ * @author Andy Wilkinson * @author Dmitriy Mayboroda */ -public class MockMvcRestDocumentationConfigurerTests { +@ExtendWith(RestDocumentationExtension.class) +class MockMvcRestDocumentationConfigurerTests { private MockHttpServletRequest request = new MockHttpServletRequest(); - @Rule - public JUnitRestDocumentation restDocumentation = new JUnitRestDocumentation(); - @Test - public void defaultConfiguration() { - RequestPostProcessor postProcessor = new MockMvcRestDocumentationConfigurer(this.restDocumentation) + void defaultConfiguration(RestDocumentationContextProvider restDocumentation) { + RequestPostProcessor postProcessor = new MockMvcRestDocumentationConfigurer(restDocumentation) .beforeMockMvcCreated(null, null); postProcessor.postProcessRequest(this.request); assertUriConfiguration("http", "localhost", 8080); } @Test - public void customScheme() { - RequestPostProcessor postProcessor = new MockMvcRestDocumentationConfigurer(this.restDocumentation).uris() + void customScheme(RestDocumentationContextProvider restDocumentation) { + RequestPostProcessor postProcessor = new MockMvcRestDocumentationConfigurer(restDocumentation).uris() .withScheme("https") .beforeMockMvcCreated(null, null); postProcessor.postProcessRequest(this.request); @@ -66,8 +65,8 @@ public void customScheme() { } @Test - public void customHost() { - RequestPostProcessor postProcessor = new MockMvcRestDocumentationConfigurer(this.restDocumentation).uris() + void customHost(RestDocumentationContextProvider restDocumentation) { + RequestPostProcessor postProcessor = new MockMvcRestDocumentationConfigurer(restDocumentation).uris() .withHost("api.example.com") .beforeMockMvcCreated(null, null); postProcessor.postProcessRequest(this.request); @@ -75,8 +74,8 @@ public void customHost() { } @Test - public void customPort() { - RequestPostProcessor postProcessor = new MockMvcRestDocumentationConfigurer(this.restDocumentation).uris() + void customPort(RestDocumentationContextProvider restDocumentation) { + RequestPostProcessor postProcessor = new MockMvcRestDocumentationConfigurer(restDocumentation).uris() .withPort(8081) .beforeMockMvcCreated(null, null); postProcessor.postProcessRequest(this.request); @@ -84,8 +83,8 @@ public void customPort() { } @Test - public void noContentLengthHeaderWhenRequestHasNotContent() { - RequestPostProcessor postProcessor = new MockMvcRestDocumentationConfigurer(this.restDocumentation).uris() + void noContentLengthHeaderWhenRequestHasNotContent(RestDocumentationContextProvider restDocumentation) { + RequestPostProcessor postProcessor = new MockMvcRestDocumentationConfigurer(restDocumentation).uris() .withPort(8081) .beforeMockMvcCreated(null, null); postProcessor.postProcessRequest(this.request); @@ -94,8 +93,8 @@ public void noContentLengthHeaderWhenRequestHasNotContent() { @Test @SuppressWarnings("unchecked") - public void uriTemplateFromRequestAttribute() { - RequestPostProcessor postProcessor = new MockMvcRestDocumentationConfigurer(this.restDocumentation) + void uriTemplateFromRequestAttribute(RestDocumentationContextProvider restDocumentation) { + RequestPostProcessor postProcessor = new MockMvcRestDocumentationConfigurer(restDocumentation) .beforeMockMvcCreated(null, null); this.request.setAttribute(RestDocumentationGenerator.ATTRIBUTE_NAME_URL_TEMPLATE, "{a}/{b}"); postProcessor.postProcessRequest(this.request); @@ -106,11 +105,11 @@ public void uriTemplateFromRequestAttribute() { @Test @SuppressWarnings("unchecked") - public void uriTemplateFromRequest() { + void uriTemplateFromRequest(RestDocumentationContextProvider restDocumentation) { Method setUriTemplate = ReflectionUtils.findMethod(MockHttpServletRequest.class, "setUriTemplate", String.class); - Assume.assumeNotNull(setUriTemplate); - RequestPostProcessor postProcessor = new MockMvcRestDocumentationConfigurer(this.restDocumentation) + Assumptions.assumeFalse(setUriTemplate == null); + RequestPostProcessor postProcessor = new MockMvcRestDocumentationConfigurer(restDocumentation) .beforeMockMvcCreated(null, null); ReflectionUtils.invokeMethod(setUriTemplate, this.request, "{a}/{b}"); postProcessor.postProcessRequest(this.request); diff --git a/spring-restdocs-mockmvc/src/test/java/org/springframework/restdocs/mockmvc/MockMvcRestDocumentationIntegrationTests.java b/spring-restdocs-mockmvc/src/test/java/org/springframework/restdocs/mockmvc/MockMvcRestDocumentationIntegrationTests.java index 3e79777a..2d83a83d 100644 --- a/spring-restdocs-mockmvc/src/test/java/org/springframework/restdocs/mockmvc/MockMvcRestDocumentationIntegrationTests.java +++ b/spring-restdocs-mockmvc/src/test/java/org/springframework/restdocs/mockmvc/MockMvcRestDocumentationIntegrationTests.java @@ -34,11 +34,10 @@ import jakarta.servlet.http.Cookie; import jakarta.servlet.http.HttpServletResponse; import org.assertj.core.api.Condition; -import org.junit.After; -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; @@ -47,7 +46,8 @@ import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; -import org.springframework.restdocs.JUnitRestDocumentation; +import org.springframework.restdocs.RestDocumentationContextProvider; +import org.springframework.restdocs.RestDocumentationExtension; import org.springframework.restdocs.mockmvc.MockMvcRestDocumentationIntegrationTests.TestConfiguration; import org.springframework.restdocs.templates.TemplateFormat; import org.springframework.restdocs.templates.TemplateFormats; @@ -56,7 +56,7 @@ import org.springframework.restdocs.testfixtures.SnippetConditions.HttpRequestCondition; import org.springframework.restdocs.testfixtures.SnippetConditions.HttpResponseCondition; import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; import org.springframework.test.context.web.WebAppConfiguration; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MvcResult; @@ -106,29 +106,30 @@ * @author Tomasz Kopczynski * @author Filip Hrisafov */ -@RunWith(SpringJUnit4ClassRunner.class) +@SpringJUnitConfig @WebAppConfiguration +@ExtendWith(RestDocumentationExtension.class) @ContextConfiguration(classes = TestConfiguration.class) public class MockMvcRestDocumentationIntegrationTests { - @Rule - public JUnitRestDocumentation restDocumentation = new JUnitRestDocumentation(); + private RestDocumentationContextProvider restDocumentation; @Autowired private WebApplicationContext context; - @Before - public void deleteSnippets() { + @BeforeEach + void setUp(RestDocumentationContextProvider restDocumentation) { + this.restDocumentation = restDocumentation; FileSystemUtils.deleteRecursively(new File("build/generated-snippets")); } - @After - public void clearOutputDirSystemProperty() { + @AfterEach + void clearOutputDirSystemProperty() { System.clearProperty("org.springframework.restdocs.outputDir"); } @Test - public void basicSnippetGeneration() throws Exception { + void basicSnippetGeneration() throws Exception { MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.context) .apply(new MockMvcRestDocumentationConfigurer(this.restDocumentation).snippets().withEncoding("UTF-8")) .build(); @@ -140,7 +141,19 @@ public void basicSnippetGeneration() throws Exception { } @Test - public void markdownSnippetGeneration() throws Exception { + void getRequestWithBody() throws Exception { + MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.context) + .apply(new MockMvcRestDocumentationConfigurer(this.restDocumentation).snippets().withEncoding("UTF-8")) + .build(); + mockMvc.perform(get("/").accept(MediaType.APPLICATION_JSON).content("some body content")) + .andExpect(status().isOk()) + .andDo(document("get-request-with-body")); + assertExpectedSnippetFilesExist(new File("build/generated-snippets/get-request-with-body"), "http-request.adoc", + "http-response.adoc", "curl-request.adoc"); + } + + @Test + void markdownSnippetGeneration() throws Exception { MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.context) .apply(new MockMvcRestDocumentationConfigurer(this.restDocumentation).snippets() .withEncoding("UTF-8") @@ -154,7 +167,7 @@ public void markdownSnippetGeneration() throws Exception { } @Test - public void curlSnippetWithContent() throws Exception { + void curlSnippetWithContent() throws Exception { MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.context) .apply(documentationConfiguration(this.restDocumentation)) .build(); @@ -168,7 +181,7 @@ public void curlSnippetWithContent() throws Exception { } @Test - public void curlSnippetWithCookies() throws Exception { + void curlSnippetWithCookies() throws Exception { MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.context) .apply(documentationConfiguration(this.restDocumentation)) .build(); @@ -182,7 +195,7 @@ public void curlSnippetWithCookies() throws Exception { } @Test - public void curlSnippetWithQueryStringOnPost() throws Exception { + void curlSnippetWithQueryStringOnPost() throws Exception { MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.context) .apply(documentationConfiguration(this.restDocumentation)) .build(); @@ -196,7 +209,7 @@ public void curlSnippetWithQueryStringOnPost() throws Exception { } @Test - public void curlSnippetWithEmptyParameterQueryString() throws Exception { + void curlSnippetWithEmptyParameterQueryString() throws Exception { MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.context) .apply(documentationConfiguration(this.restDocumentation)) .build(); @@ -210,7 +223,7 @@ public void curlSnippetWithEmptyParameterQueryString() throws Exception { } @Test - public void curlSnippetWithContentAndParametersOnPost() throws Exception { + void curlSnippetWithContentAndParametersOnPost() throws Exception { MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.context) .apply(documentationConfiguration(this.restDocumentation)) .build(); @@ -224,7 +237,7 @@ public void curlSnippetWithContentAndParametersOnPost() throws Exception { } @Test - public void httpieSnippetWithContent() throws Exception { + void httpieSnippetWithContent() throws Exception { MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.context) .apply(documentationConfiguration(this.restDocumentation)) .build(); @@ -237,7 +250,7 @@ public void httpieSnippetWithContent() throws Exception { } @Test - public void httpieSnippetWithCookies() throws Exception { + void httpieSnippetWithCookies() throws Exception { MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.context) .apply(documentationConfiguration(this.restDocumentation)) .build(); @@ -251,7 +264,7 @@ public void httpieSnippetWithCookies() throws Exception { } @Test - public void httpieSnippetWithQueryStringOnPost() throws Exception { + void httpieSnippetWithQueryStringOnPost() throws Exception { MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.context) .apply(documentationConfiguration(this.restDocumentation)) .build(); @@ -265,7 +278,7 @@ public void httpieSnippetWithQueryStringOnPost() throws Exception { } @Test - public void httpieSnippetWithContentAndParametersOnPost() throws Exception { + void httpieSnippetWithContentAndParametersOnPost() throws Exception { MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.context) .apply(documentationConfiguration(this.restDocumentation)) .build(); @@ -280,7 +293,7 @@ public void httpieSnippetWithContentAndParametersOnPost() throws Exception { } @Test - public void linksSnippet() throws Exception { + void linksSnippet() throws Exception { MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.context) .apply(documentationConfiguration(this.restDocumentation)) .build(); @@ -293,7 +306,7 @@ public void linksSnippet() throws Exception { } @Test - public void pathParametersSnippet() throws Exception { + void pathParametersSnippet() throws Exception { MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.context) .apply(documentationConfiguration(this.restDocumentation)) .build(); @@ -305,7 +318,7 @@ public void pathParametersSnippet() throws Exception { } @Test - public void queryParametersSnippet() throws Exception { + void queryParametersSnippet() throws Exception { MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.context) .apply(documentationConfiguration(this.restDocumentation)) .build(); @@ -317,7 +330,7 @@ public void queryParametersSnippet() throws Exception { } @Test - public void requestFieldsSnippet() throws Exception { + void requestFieldsSnippet() throws Exception { MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.context) .apply(documentationConfiguration(this.restDocumentation)) .build(); @@ -329,7 +342,7 @@ public void requestFieldsSnippet() throws Exception { } @Test - public void requestPartsSnippet() throws Exception { + void requestPartsSnippet() throws Exception { MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.context) .apply(documentationConfiguration(this.restDocumentation)) .build(); @@ -341,7 +354,7 @@ public void requestPartsSnippet() throws Exception { } @Test - public void responseFieldsSnippet() throws Exception { + void responseFieldsSnippet() throws Exception { MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.context) .apply(documentationConfiguration(this.restDocumentation)) .build(); @@ -354,7 +367,7 @@ public void responseFieldsSnippet() throws Exception { } @Test - public void responseWithSetCookie() throws Exception { + void responseWithSetCookie() throws Exception { MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.context) .apply(documentationConfiguration(this.restDocumentation)) .build(); @@ -368,7 +381,7 @@ public void responseWithSetCookie() throws Exception { } @Test - public void parameterizedOutputDirectory() throws Exception { + void parameterizedOutputDirectory() throws Exception { MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.context) .apply(documentationConfiguration(this.restDocumentation)) .build(); @@ -380,7 +393,7 @@ public void parameterizedOutputDirectory() throws Exception { } @Test - public void multiStep() throws Exception { + void multiStep() throws Exception { MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.context) .apply(documentationConfiguration(this.restDocumentation)) .alwaysDo(document("{method-name}-{step}")) @@ -398,7 +411,7 @@ public void multiStep() throws Exception { } @Test - public void alwaysDoWithAdditionalSnippets() throws Exception { + void alwaysDoWithAdditionalSnippets() throws Exception { RestDocumentationResultHandler documentation = document("{method-name}-{step}"); MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.context) .apply(documentationConfiguration(this.restDocumentation)) @@ -412,7 +425,7 @@ public void alwaysDoWithAdditionalSnippets() throws Exception { } @Test - public void preprocessedRequest() throws Exception { + void preprocessedRequest() throws Exception { MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.context) .apply(documentationConfiguration(this.restDocumentation)) .build(); @@ -455,7 +468,7 @@ public void preprocessedRequest() throws Exception { } @Test - public void defaultPreprocessedRequest() throws Exception { + void defaultPreprocessedRequest() throws Exception { Pattern pattern = Pattern.compile("(\"alpha\")"); MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.context) .apply(documentationConfiguration(this.restDocumentation).operationPreprocessors() @@ -486,7 +499,7 @@ public void defaultPreprocessedRequest() throws Exception { } @Test - public void preprocessedResponse() throws Exception { + void preprocessedResponse() throws Exception { MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.context) .apply(documentationConfiguration(this.restDocumentation)) .build(); @@ -515,7 +528,7 @@ public void preprocessedResponse() throws Exception { } @Test - public void defaultPreprocessedResponse() throws Exception { + void defaultPreprocessedResponse() throws Exception { Pattern pattern = Pattern.compile("(\"alpha\")"); MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.context) .apply(documentationConfiguration(this.restDocumentation).operationPreprocessors() @@ -537,7 +550,7 @@ public void defaultPreprocessedResponse() throws Exception { } @Test - public void customSnippetTemplate() throws Exception { + void customSnippetTemplate() throws Exception { MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.context) .apply(documentationConfiguration(this.restDocumentation)) .build(); @@ -559,7 +572,7 @@ public void customSnippetTemplate() throws Exception { } @Test - public void customContextPath() throws Exception { + void customContextPath() throws Exception { MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.context) .apply(documentationConfiguration(this.restDocumentation)) .build(); @@ -573,7 +586,7 @@ public void customContextPath() throws Exception { } @Test - public void exceptionShouldBeThrownWhenCallDocumentMockMvcNotConfigured() { + void exceptionShouldBeThrownWhenCallDocumentMockMvcNotConfigured() { MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.context).build(); assertThatThrownBy(() -> mockMvc.perform(get("/").accept(MediaType.APPLICATION_JSON)).andDo(document("basic"))) .isInstanceOf(IllegalStateException.class) @@ -583,7 +596,7 @@ public void exceptionShouldBeThrownWhenCallDocumentMockMvcNotConfigured() { } @Test - public void exceptionShouldBeThrownWhenCallDocumentSnippetsMockMvcNotConfigured() { + void exceptionShouldBeThrownWhenCallDocumentSnippetsMockMvcNotConfigured() { RestDocumentationResultHandler documentation = document("{method-name}-{step}"); MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.context).build(); assertThatThrownBy(() -> mockMvc.perform(get("/").accept(MediaType.APPLICATION_JSON)) @@ -594,7 +607,7 @@ public void exceptionShouldBeThrownWhenCallDocumentSnippetsMockMvcNotConfigured( } @Test - public void multiPart() throws Exception { + void multiPart() throws Exception { MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.context) .apply(documentationConfiguration(this.restDocumentation)) .build(); diff --git a/spring-restdocs-mockmvc/src/test/java/org/springframework/restdocs/mockmvc/RestDocumentationRequestBuildersTests.java b/spring-restdocs-mockmvc/src/test/java/org/springframework/restdocs/mockmvc/RestDocumentationRequestBuildersTests.java index 0d35b6d2..80e9d9c3 100644 --- a/spring-restdocs-mockmvc/src/test/java/org/springframework/restdocs/mockmvc/RestDocumentationRequestBuildersTests.java +++ b/spring-restdocs-mockmvc/src/test/java/org/springframework/restdocs/mockmvc/RestDocumentationRequestBuildersTests.java @@ -19,7 +19,7 @@ import java.net.URI; import jakarta.servlet.ServletContext; -import org.junit.Test; +import org.junit.jupiter.api.Test; import org.springframework.http.HttpMethod; import org.springframework.mock.web.MockHttpServletRequest; @@ -44,97 +44,97 @@ * @author Andy Wilkinson * */ -public class RestDocumentationRequestBuildersTests { +class RestDocumentationRequestBuildersTests { private final ServletContext servletContext = new MockServletContext(); @Test - public void getTemplate() { + void getTemplate() { assertTemplate(get("/{template}", "t"), HttpMethod.GET); } @Test - public void getUri() { + void getUri() { assertUri(get(URI.create("/uri")), HttpMethod.GET); } @Test - public void postTemplate() { + void postTemplate() { assertTemplate(post("/{template}", "t"), HttpMethod.POST); } @Test - public void postUri() { + void postUri() { assertUri(post(URI.create("/uri")), HttpMethod.POST); } @Test - public void putTemplate() { + void putTemplate() { assertTemplate(put("/{template}", "t"), HttpMethod.PUT); } @Test - public void putUri() { + void putUri() { assertUri(put(URI.create("/uri")), HttpMethod.PUT); } @Test - public void patchTemplate() { + void patchTemplate() { assertTemplate(patch("/{template}", "t"), HttpMethod.PATCH); } @Test - public void patchUri() { + void patchUri() { assertUri(patch(URI.create("/uri")), HttpMethod.PATCH); } @Test - public void deleteTemplate() { + void deleteTemplate() { assertTemplate(delete("/{template}", "t"), HttpMethod.DELETE); } @Test - public void deleteUri() { + void deleteUri() { assertUri(delete(URI.create("/uri")), HttpMethod.DELETE); } @Test - public void optionsTemplate() { + void optionsTemplate() { assertTemplate(options("/{template}", "t"), HttpMethod.OPTIONS); } @Test - public void optionsUri() { + void optionsUri() { assertUri(options(URI.create("/uri")), HttpMethod.OPTIONS); } @Test - public void headTemplate() { + void headTemplate() { assertTemplate(head("/{template}", "t"), HttpMethod.HEAD); } @Test - public void headUri() { + void headUri() { assertUri(head(URI.create("/uri")), HttpMethod.HEAD); } @Test - public void requestTemplate() { + void requestTemplate() { assertTemplate(request(HttpMethod.GET, "/{template}", "t"), HttpMethod.GET); } @Test - public void requestUri() { + void requestUri() { assertUri(request(HttpMethod.GET, URI.create("/uri")), HttpMethod.GET); } @Test - public void multipartTemplate() { + void multipartTemplate() { assertTemplate(multipart("/{template}", "t"), HttpMethod.POST); } @Test - public void multipartUri() { + void multipartUri() { assertUri(multipart(URI.create("/uri")), HttpMethod.POST); } diff --git a/spring-restdocs-platform/build.gradle b/spring-restdocs-platform/build.gradle index 973cf732..966b90d9 100644 --- a/spring-restdocs-platform/build.gradle +++ b/spring-restdocs-platform/build.gradle @@ -8,24 +8,26 @@ javaPlatform { dependencies { constraints { + api("com.google.code.findbugs:jsr305:3.0.2") api("com.samskivert:jmustache:$jmustacheVersion") - api("jakarta.servlet:jakarta.servlet-api:6.0.0") - api("jakarta.validation:jakarta.validation-api:3.0.0") - api("junit:junit:4.13.1") - api("org.apache.pdfbox:pdfbox:2.0.27") - api("org.apache.tomcat.embed:tomcat-embed-core:10.1.1") - api("org.apache.tomcat.embed:tomcat-embed-el:10.1.1") - api("org.asciidoctor:asciidoctorj:2.5.7") - api("org.asciidoctor:asciidoctorj-pdf:2.3.3") - api("org.assertj:assertj-core:3.23.1") + api("jakarta.servlet:jakarta.servlet-api:6.1.0") + api("jakarta.validation:jakarta.validation-api:3.1.0") + api("org.apache.pdfbox:pdfbox:3.0.5") + api("org.apache.tomcat.embed:tomcat-embed-core:11.0.9") + api("org.apache.tomcat.embed:tomcat-embed-el:11.0.9") + api("org.apiguardian:apiguardian-api:1.1.2") + api("org.asciidoctor:asciidoctorj:3.0.0") + api("org.asciidoctor:asciidoctorj-pdf:2.3.19") + api("org.assertj:assertj-core:3.27.2") api("org.hamcrest:hamcrest-core:1.3") api("org.hamcrest:hamcrest-library:1.3") - api("org.hibernate.validator:hibernate-validator:8.0.0.Final") - api("org.javamoney:moneta:1.4.2") - api("org.junit.jupiter:junit-jupiter-api:5.0.0") + api("org.hibernate.validator:hibernate-validator:9.0.1.Final") + api("org.jspecify:jspecify:1.0.0") + api("org.javamoney:moneta:1.4.5") } - 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.5.2")) api(enforcedPlatform("org.mockito:mockito-bom:4.9.0")) + api(enforcedPlatform("org.junit:junit-bom:5.13.3")) api(enforcedPlatform("org.springframework:spring-framework-bom:$springFrameworkVersion")) + api(enforcedPlatform("tools.jackson:jackson-bom:3.0.0-rc6")) } diff --git a/spring-restdocs-restassured/build.gradle b/spring-restdocs-restassured/build.gradle index 55431184..c77c07b0 100644 --- a/spring-restdocs-restassured/build.gradle +++ b/spring-restdocs-restassured/build.gradle @@ -1,5 +1,5 @@ plugins { - id "io.spring.compatibility-test" version "0.0.4" + id 'org.springframework.restdocs.conventions' id "java-library" id "maven-publish" } @@ -8,25 +8,12 @@ description = "Spring REST Docs REST Assured" dependencies { api(project(":spring-restdocs-core")) - api("io.rest-assured:rest-assured") { - exclude group: "commons-logging", module: "commons-logging" - } + api("io.rest-assured:rest-assured") implementation("org.springframework:spring-web") - internal(platform(project(":spring-restdocs-platform"))) - + testCompileOnly("org.apiguardian:apiguardian-api") testImplementation(testFixtures(project(":spring-restdocs-core"))) testImplementation("com.fasterxml.jackson.core:jackson-databind") - testImplementation("junit:junit") testImplementation("org.apache.tomcat.embed:tomcat-embed-core") - testImplementation("org.assertj:assertj-core") - testImplementation("org.hamcrest:hamcrest-library") - testImplementation("org.mockito:mockito-core") -} - -compatibilityTest { - dependency("REST Assured") { restAssured -> - restAssured.groupId = "io.rest-assured" - restAssured.versions = ["5.3.+", "5.4.+", "5.5.+"] - } + testImplementation("tools.jackson.core:jackson-databind") } diff --git a/spring-restdocs-restassured/src/main/java/org/springframework/restdocs/restassured/package-info.java b/spring-restdocs-restassured/src/main/java/org/springframework/restdocs/restassured/package-info.java index 6bfd4ba5..36081746 100644 --- a/spring-restdocs-restassured/src/main/java/org/springframework/restdocs/restassured/package-info.java +++ b/spring-restdocs-restassured/src/main/java/org/springframework/restdocs/restassured/package-info.java @@ -17,4 +17,7 @@ /** * Core classes for using Spring REST Docs with REST Assured. */ +@NullMarked package org.springframework.restdocs.restassured; + +import org.jspecify.annotations.NullMarked; diff --git a/spring-restdocs-restassured/src/test/java/org/springframework/restdocs/restassured/RestAssuredParameterBehaviorTests.java b/spring-restdocs-restassured/src/test/java/org/springframework/restdocs/restassured/RestAssuredParameterBehaviorTests.java index 160b6d21..4d41500b 100644 --- a/spring-restdocs-restassured/src/test/java/org/springframework/restdocs/restassured/RestAssuredParameterBehaviorTests.java +++ b/spring-restdocs-restassured/src/test/java/org/springframework/restdocs/restassured/RestAssuredParameterBehaviorTests.java @@ -19,8 +19,8 @@ import io.restassured.RestAssured; import io.restassured.specification.RequestSpecification; import org.assertj.core.api.AbstractAssert; -import org.junit.ClassRule; -import org.junit.Test; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; import org.springframework.http.HttpMethod; import org.springframework.http.MediaType; @@ -34,12 +34,12 @@ * * @author Andy Wilkinson */ -public class RestAssuredParameterBehaviorTests { +class RestAssuredParameterBehaviorTests { private static final MediaType APPLICATION_FORM_URLENCODED_ISO_8859_1 = MediaType .parseMediaType(MediaType.APPLICATION_FORM_URLENCODED_VALUE + ";charset=ISO-8859-1"); - @ClassRule + @RegisterExtension public static TomcatServer tomcat = new TomcatServer(); private final RestAssuredRequestConverter factory = new RestAssuredRequestConverter(); @@ -54,7 +54,7 @@ public class RestAssuredParameterBehaviorTests { }); @Test - public void queryParameterOnGet() { + void queryParameterOnGet() { this.spec.queryParam("a", "alpha", "apple") .queryParam("b", "bravo") .get("/query-parameter") @@ -64,7 +64,7 @@ public void queryParameterOnGet() { } @Test - public void queryParameterOnHead() { + void queryParameterOnHead() { this.spec.queryParam("a", "alpha", "apple") .queryParam("b", "bravo") .head("/query-parameter") @@ -74,7 +74,7 @@ public void queryParameterOnHead() { } @Test - public void queryParameterOnPost() { + void queryParameterOnPost() { this.spec.queryParam("a", "alpha", "apple") .queryParam("b", "bravo") .post("/query-parameter") @@ -84,7 +84,7 @@ public void queryParameterOnPost() { } @Test - public void queryParameterOnPut() { + void queryParameterOnPut() { this.spec.queryParam("a", "alpha", "apple") .queryParam("b", "bravo") .put("/query-parameter") @@ -94,7 +94,7 @@ public void queryParameterOnPut() { } @Test - public void queryParameterOnPatch() { + void queryParameterOnPatch() { this.spec.queryParam("a", "alpha", "apple") .queryParam("b", "bravo") .patch("/query-parameter") @@ -104,7 +104,7 @@ public void queryParameterOnPatch() { } @Test - public void queryParameterOnDelete() { + void queryParameterOnDelete() { this.spec.queryParam("a", "alpha", "apple") .queryParam("b", "bravo") .delete("/query-parameter") @@ -114,7 +114,7 @@ public void queryParameterOnDelete() { } @Test - public void queryParameterOnOptions() { + void queryParameterOnOptions() { this.spec.queryParam("a", "alpha", "apple") .queryParam("b", "bravo") .options("/query-parameter") @@ -124,49 +124,49 @@ public void queryParameterOnOptions() { } @Test - public void paramOnGet() { + void paramOnGet() { this.spec.param("a", "alpha", "apple").param("b", "bravo").get("/query-parameter").then().statusCode(200); assertThatRequest(this.request).hasQueryParametersWithMethod(HttpMethod.GET); } @Test - public void paramOnHead() { + void paramOnHead() { this.spec.param("a", "alpha", "apple").param("b", "bravo").head("/query-parameter").then().statusCode(200); assertThatRequest(this.request).hasQueryParametersWithMethod(HttpMethod.HEAD); } @Test - public void paramOnPost() { + void paramOnPost() { this.spec.param("a", "alpha", "apple").param("b", "bravo").post("/form-url-encoded").then().statusCode(200); assertThatRequest(this.request).isFormUrlEncodedWithMethod(HttpMethod.POST); } @Test - public void paramOnPut() { + void paramOnPut() { this.spec.param("a", "alpha", "apple").param("b", "bravo").put("/query-parameter").then().statusCode(200); assertThatRequest(this.request).hasQueryParametersWithMethod(HttpMethod.PUT); } @Test - public void paramOnPatch() { + void paramOnPatch() { this.spec.param("a", "alpha", "apple").param("b", "bravo").patch("/query-parameter").then().statusCode(200); assertThatRequest(this.request).hasQueryParametersWithMethod(HttpMethod.PATCH); } @Test - public void paramOnDelete() { + void paramOnDelete() { this.spec.param("a", "alpha", "apple").param("b", "bravo").delete("/query-parameter").then().statusCode(200); assertThatRequest(this.request).hasQueryParametersWithMethod(HttpMethod.DELETE); } @Test - public void paramOnOptions() { + void paramOnOptions() { this.spec.param("a", "alpha", "apple").param("b", "bravo").options("/query-parameter").then().statusCode(200); assertThatRequest(this.request).hasQueryParametersWithMethod(HttpMethod.OPTIONS); } @Test - public void formParamOnGet() { + void formParamOnGet() { this.spec.formParam("a", "alpha", "apple") .formParam("b", "bravo") .get("/query-parameter") @@ -176,7 +176,7 @@ public void formParamOnGet() { } @Test - public void formParamOnHead() { + void formParamOnHead() { this.spec.formParam("a", "alpha", "apple") .formParam("b", "bravo") .head("/form-url-encoded") @@ -186,7 +186,7 @@ public void formParamOnHead() { } @Test - public void formParamOnPost() { + void formParamOnPost() { this.spec.formParam("a", "alpha", "apple") .formParam("b", "bravo") .post("/form-url-encoded") @@ -196,7 +196,7 @@ public void formParamOnPost() { } @Test - public void formParamOnPut() { + void formParamOnPut() { this.spec.formParam("a", "alpha", "apple") .formParam("b", "bravo") .put("/form-url-encoded") @@ -206,7 +206,7 @@ public void formParamOnPut() { } @Test - public void formParamOnPatch() { + void formParamOnPatch() { this.spec.formParam("a", "alpha", "apple") .formParam("b", "bravo") .patch("/form-url-encoded") @@ -216,7 +216,7 @@ public void formParamOnPatch() { } @Test - public void formParamOnDelete() { + void formParamOnDelete() { this.spec.formParam("a", "alpha", "apple") .formParam("b", "bravo") .delete("/form-url-encoded") @@ -226,7 +226,7 @@ public void formParamOnDelete() { } @Test - public void formParamOnOptions() { + void formParamOnOptions() { this.spec.formParam("a", "alpha", "apple") .formParam("b", "bravo") .options("/form-url-encoded") diff --git a/spring-restdocs-restassured/src/test/java/org/springframework/restdocs/restassured/RestAssuredRequestConverterTests.java b/spring-restdocs-restassured/src/test/java/org/springframework/restdocs/restassured/RestAssuredRequestConverterTests.java index 8a813236..821c6a9e 100644 --- a/spring-restdocs-restassured/src/test/java/org/springframework/restdocs/restassured/RestAssuredRequestConverterTests.java +++ b/spring-restdocs-restassured/src/test/java/org/springframework/restdocs/restassured/RestAssuredRequestConverterTests.java @@ -28,8 +28,8 @@ import io.restassured.RestAssured; import io.restassured.specification.FilterableRequestSpecification; import io.restassured.specification.RequestSpecification; -import org.junit.ClassRule; -import org.junit.Test; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; @@ -40,21 +40,22 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatIllegalStateException; +import static org.assertj.core.api.Assertions.entry; /** * Tests for {@link RestAssuredRequestConverter}. * * @author Andy Wilkinson */ -public class RestAssuredRequestConverterTests { +class RestAssuredRequestConverterTests { - @ClassRule + @RegisterExtension public static TomcatServer tomcat = new TomcatServer(); private final RestAssuredRequestConverter factory = new RestAssuredRequestConverter(); @Test - public void requestUri() { + void requestUri() { RequestSpecification requestSpec = RestAssured.given().port(tomcat.getPort()); requestSpec.get("/foo/bar"); OperationRequest request = this.factory.convert((FilterableRequestSpecification) requestSpec); @@ -62,7 +63,7 @@ public void requestUri() { } @Test - public void requestMethod() { + void requestMethod() { RequestSpecification requestSpec = RestAssured.given().port(tomcat.getPort()); requestSpec.head("/foo/bar"); OperationRequest request = this.factory.convert((FilterableRequestSpecification) requestSpec); @@ -70,7 +71,7 @@ public void requestMethod() { } @Test - public void queryStringParameters() { + void queryStringParameters() { RequestSpecification requestSpec = RestAssured.given().port(tomcat.getPort()).queryParam("foo", "bar"); requestSpec.get("/"); OperationRequest request = this.factory.convert((FilterableRequestSpecification) requestSpec); @@ -78,7 +79,7 @@ public void queryStringParameters() { } @Test - public void queryStringFromUrlParameters() { + void queryStringFromUrlParameters() { RequestSpecification requestSpec = RestAssured.given().port(tomcat.getPort()); requestSpec.get("/?foo=bar&foo=qix"); OperationRequest request = this.factory.convert((FilterableRequestSpecification) requestSpec); @@ -86,7 +87,7 @@ public void queryStringFromUrlParameters() { } @Test - public void paramOnGetRequestIsMappedToQueryString() { + void paramOnGetRequestIsMappedToQueryString() { RequestSpecification requestSpec = RestAssured.given().port(tomcat.getPort()).param("foo", "bar"); requestSpec.get("/"); OperationRequest request = this.factory.convert((FilterableRequestSpecification) requestSpec); @@ -94,33 +95,29 @@ public void paramOnGetRequestIsMappedToQueryString() { } @Test - public void headers() { + void headers() { RequestSpecification requestSpec = RestAssured.given().port(tomcat.getPort()).header("Foo", "bar"); requestSpec.get("/"); OperationRequest request = this.factory.convert((FilterableRequestSpecification) requestSpec); - assertThat(request.getHeaders()).hasSize(2); - assertThat(request.getHeaders()).containsEntry("Foo", Collections.singletonList("bar")); - assertThat(request.getHeaders()).containsEntry("Host", - Collections.singletonList("localhost:" + tomcat.getPort())); + assertThat(request.getHeaders().headerSet()).containsOnly(entry("Foo", Collections.singletonList("bar")), + entry("Host", Collections.singletonList("localhost:" + tomcat.getPort()))); } @Test - public void headersWithCustomAccept() { + void headersWithCustomAccept() { RequestSpecification requestSpec = RestAssured.given() .port(tomcat.getPort()) .header("Foo", "bar") .accept("application/json"); requestSpec.get("/"); OperationRequest request = this.factory.convert((FilterableRequestSpecification) requestSpec); - assertThat(request.getHeaders()).hasSize(3); - assertThat(request.getHeaders()).containsEntry("Foo", Collections.singletonList("bar")); - assertThat(request.getHeaders()).containsEntry("Accept", Collections.singletonList("application/json")); - assertThat(request.getHeaders()).containsEntry("Host", - Collections.singletonList("localhost:" + tomcat.getPort())); + assertThat(request.getHeaders().headerSet()).containsOnly(entry("Foo", Collections.singletonList("bar")), + entry("Accept", Collections.singletonList("application/json")), + entry("Host", Collections.singletonList("localhost:" + tomcat.getPort()))); } @Test - public void cookies() { + void cookies() { RequestSpecification requestSpec = RestAssured.given() .port(tomcat.getPort()) .cookie("cookie1", "cookieVal1") @@ -141,7 +138,7 @@ public void cookies() { } @Test - public void multipart() { + void multipart() { RequestSpecification requestSpec = RestAssured.given() .port(tomcat.getPort()) .multiPart("a", "a.txt", "alpha", null) @@ -153,21 +150,20 @@ public void multipart() { assertThat(parts).extracting("name").containsExactly("a", "b"); assertThat(parts).extracting("submittedFileName").containsExactly("a.txt", "file"); assertThat(parts).extracting("contentAsString").containsExactly("alpha", "{\"foo\":\"bar\"}"); - assertThat(parts).extracting("headers") - .extracting(HttpHeaders.CONTENT_TYPE) + assertThat(parts).map((part) -> part.getHeaders().get(HttpHeaders.CONTENT_TYPE)) .containsExactly(Collections.singletonList(MediaType.TEXT_PLAIN_VALUE), Collections.singletonList(MediaType.APPLICATION_JSON_VALUE)); } @Test - public void byteArrayBody() { + void byteArrayBody() { RequestSpecification requestSpec = RestAssured.given().body("body".getBytes()).port(tomcat.getPort()); requestSpec.post(); this.factory.convert((FilterableRequestSpecification) requestSpec); } @Test - public void stringBody() { + void stringBody() { RequestSpecification requestSpec = RestAssured.given().body("body").port(tomcat.getPort()); requestSpec.post(); OperationRequest request = this.factory.convert((FilterableRequestSpecification) requestSpec); @@ -175,7 +171,7 @@ public void stringBody() { } @Test - public void objectBody() { + void objectBody() { RequestSpecification requestSpec = RestAssured.given().body(new ObjectBody("bar")).port(tomcat.getPort()); requestSpec.post(); OperationRequest request = this.factory.convert((FilterableRequestSpecification) requestSpec); @@ -183,7 +179,7 @@ public void objectBody() { } @Test - public void byteArrayInputStreamBody() { + void byteArrayInputStreamBody() { RequestSpecification requestSpec = RestAssured.given() .body(new ByteArrayInputStream(new byte[] { 1, 2, 3, 4 })) .port(tomcat.getPort()); @@ -193,7 +189,7 @@ public void byteArrayInputStreamBody() { } @Test - public void fileBody() { + void fileBody() { RequestSpecification requestSpec = RestAssured.given() .body(new File("src/test/resources/body.txt")) .port(tomcat.getPort()); @@ -203,7 +199,7 @@ public void fileBody() { } @Test - public void fileInputStreamBody() throws FileNotFoundException { + void fileInputStreamBody() throws FileNotFoundException { FileInputStream inputStream = new FileInputStream("src/test/resources/body.txt"); RequestSpecification requestSpec = RestAssured.given().body(inputStream).port(tomcat.getPort()); requestSpec.post(); @@ -213,7 +209,7 @@ public void fileInputStreamBody() throws FileNotFoundException { } @Test - public void multipartWithByteArrayInputStreamBody() { + void multipartWithByteArrayInputStreamBody() { RequestSpecification requestSpec = RestAssured.given() .port(tomcat.getPort()) .multiPart("foo", "foo.txt", new ByteArrayInputStream("foo".getBytes())); @@ -223,7 +219,7 @@ public void multipartWithByteArrayInputStreamBody() { } @Test - public void multipartWithStringBody() { + void multipartWithStringBody() { RequestSpecification requestSpec = RestAssured.given().port(tomcat.getPort()).multiPart("control", "foo"); requestSpec.post(); OperationRequest request = this.factory.convert((FilterableRequestSpecification) requestSpec); @@ -231,7 +227,7 @@ public void multipartWithStringBody() { } @Test - public void multipartWithByteArrayBody() { + void multipartWithByteArrayBody() { RequestSpecification requestSpec = RestAssured.given() .port(tomcat.getPort()) .multiPart("control", "file", "foo".getBytes()); @@ -241,7 +237,7 @@ public void multipartWithByteArrayBody() { } @Test - public void multipartWithFileBody() { + void multipartWithFileBody() { RequestSpecification requestSpec = RestAssured.given() .port(tomcat.getPort()) .multiPart(new File("src/test/resources/body.txt")); @@ -251,7 +247,7 @@ public void multipartWithFileBody() { } @Test - public void multipartWithFileInputStreamBody() throws FileNotFoundException { + void multipartWithFileInputStreamBody() throws FileNotFoundException { FileInputStream inputStream = new FileInputStream("src/test/resources/body.txt"); RequestSpecification requestSpec = RestAssured.given() .port(tomcat.getPort()) @@ -263,7 +259,7 @@ public void multipartWithFileInputStreamBody() throws FileNotFoundException { } @Test - public void multipartWithObjectBody() { + void multipartWithObjectBody() { RequestSpecification requestSpec = RestAssured.given() .port(tomcat.getPort()) .multiPart("control", new ObjectBody("bar")); diff --git a/spring-restdocs-restassured/src/test/java/org/springframework/restdocs/restassured/RestAssuredResponseConverterTests.java b/spring-restdocs-restassured/src/test/java/org/springframework/restdocs/restassured/RestAssuredResponseConverterTests.java index 2afed8a4..f97d5bed 100644 --- a/spring-restdocs-restassured/src/test/java/org/springframework/restdocs/restassured/RestAssuredResponseConverterTests.java +++ b/spring-restdocs-restassured/src/test/java/org/springframework/restdocs/restassured/RestAssuredResponseConverterTests.java @@ -19,7 +19,7 @@ import io.restassured.http.Headers; import io.restassured.response.Response; import io.restassured.response.ResponseBody; -import org.junit.Test; +import org.junit.jupiter.api.Test; import org.springframework.http.HttpStatusCode; import org.springframework.restdocs.operation.OperationResponse; @@ -33,12 +33,12 @@ * * @author Andy Wilkinson */ -public class RestAssuredResponseConverterTests { +class RestAssuredResponseConverterTests { private final RestAssuredResponseConverter converter = new RestAssuredResponseConverter(); @Test - public void responseWithCustomStatus() { + void responseWithCustomStatus() { Response response = mock(Response.class); given(response.getStatusCode()).willReturn(600); given(response.getHeaders()).willReturn(new Headers()); diff --git a/spring-restdocs-restassured/src/test/java/org/springframework/restdocs/restassured/RestAssuredRestDocumentationConfigurerTests.java b/spring-restdocs-restassured/src/test/java/org/springframework/restdocs/restassured/RestAssuredRestDocumentationConfigurerTests.java index 66d5259b..0dcfbbb1 100644 --- a/spring-restdocs-restassured/src/test/java/org/springframework/restdocs/restassured/RestAssuredRestDocumentationConfigurerTests.java +++ b/spring-restdocs-restassured/src/test/java/org/springframework/restdocs/restassured/RestAssuredRestDocumentationConfigurerTests.java @@ -22,11 +22,13 @@ import io.restassured.filter.FilterContext; import io.restassured.specification.FilterableRequestSpecification; import io.restassured.specification.FilterableResponseSpecification; -import org.junit.Rule; -import org.junit.Test; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.ArgumentCaptor; -import org.springframework.restdocs.JUnitRestDocumentation; +import org.springframework.restdocs.RestDocumentationContextProvider; +import org.springframework.restdocs.RestDocumentationExtension; import org.springframework.restdocs.generate.RestDocumentationGenerator; import org.springframework.restdocs.operation.preprocess.OperationRequestPreprocessor; import org.springframework.restdocs.operation.preprocess.OperationResponsePreprocessor; @@ -45,10 +47,8 @@ * @author Andy Wilkinson * @author Filip Hrisafov */ -public class RestAssuredRestDocumentationConfigurerTests { - - @Rule - public final JUnitRestDocumentation restDocumentation = new JUnitRestDocumentation(); +@ExtendWith(RestDocumentationExtension.class) +class RestAssuredRestDocumentationConfigurerTests { private final FilterableRequestSpecification requestSpec = mock(FilterableRequestSpecification.class); @@ -56,17 +56,21 @@ public class RestAssuredRestDocumentationConfigurerTests { private final FilterContext filterContext = mock(FilterContext.class); - private final RestAssuredRestDocumentationConfigurer configurer = new RestAssuredRestDocumentationConfigurer( - this.restDocumentation); + private RestAssuredRestDocumentationConfigurer configurer; + + @BeforeEach + void setUp(RestDocumentationContextProvider restDocumentation) { + this.configurer = new RestAssuredRestDocumentationConfigurer(restDocumentation); + } @Test - public void nextFilterIsCalled() { + void nextFilterIsCalled() { this.configurer.filter(this.requestSpec, this.responseSpec, this.filterContext); verify(this.filterContext).next(this.requestSpec, this.responseSpec); } @Test - public void configurationIsAddedToTheContext() { + void configurationIsAddedToTheContext() { this.configurer.operationPreprocessors() .withRequestDefaults(Preprocessors.prettyPrint()) .withResponseDefaults(Preprocessors.modifyHeaders().remove("Foo")) diff --git a/spring-restdocs-restassured/src/test/java/org/springframework/restdocs/restassured/RestAssuredRestDocumentationIntegrationTests.java b/spring-restdocs-restassured/src/test/java/org/springframework/restdocs/restassured/RestAssuredRestDocumentationIntegrationTests.java index 6cd27751..d6ac777c 100644 --- a/spring-restdocs-restassured/src/test/java/org/springframework/restdocs/restassured/RestAssuredRestDocumentationIntegrationTests.java +++ b/spring-restdocs-restassured/src/test/java/org/springframework/restdocs/restassured/RestAssuredRestDocumentationIntegrationTests.java @@ -29,14 +29,15 @@ import io.restassured.builder.RequestSpecBuilder; import io.restassured.specification.RequestSpecification; import org.assertj.core.api.Condition; -import org.junit.ClassRule; -import org.junit.Rule; -import org.junit.Test; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.api.extension.RegisterExtension; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; -import org.springframework.restdocs.JUnitRestDocumentation; +import org.springframework.restdocs.RestDocumentationContextProvider; +import org.springframework.restdocs.RestDocumentationExtension; import org.springframework.restdocs.templates.TemplateFormat; import org.springframework.restdocs.templates.TemplateFormats; import org.springframework.restdocs.testfixtures.SnippetConditions; @@ -80,18 +81,16 @@ * @author Tomasz Kopczynski * @author Filip Hrisafov */ -public class RestAssuredRestDocumentationIntegrationTests { +@ExtendWith(RestDocumentationExtension.class) +class RestAssuredRestDocumentationIntegrationTests { - @Rule - public JUnitRestDocumentation restDocumentation = new JUnitRestDocumentation(); - - @ClassRule - public static TomcatServer tomcat = new TomcatServer(); + @RegisterExtension + private static TomcatServer tomcat = new TomcatServer(); @Test - public void defaultSnippetGeneration() { + void defaultSnippetGeneration(RestDocumentationContextProvider restDocumentation) { given().port(tomcat.getPort()) - .filter(documentationConfiguration(this.restDocumentation)) + .filter(documentationConfiguration(restDocumentation)) .filter(document("default")) .get("/") .then() @@ -101,10 +100,10 @@ public void defaultSnippetGeneration() { } @Test - public void curlSnippetWithContent() { + void curlSnippetWithContent(RestDocumentationContextProvider restDocumentation) { String contentType = "text/plain; charset=UTF-8"; given().port(tomcat.getPort()) - .filter(documentationConfiguration(this.restDocumentation)) + .filter(documentationConfiguration(restDocumentation)) .filter(document("curl-snippet-with-content")) .accept("application/json") .body("content") @@ -120,10 +119,10 @@ public void curlSnippetWithContent() { } @Test - public void curlSnippetWithCookies() { + void curlSnippetWithCookies(RestDocumentationContextProvider restDocumentation) { String contentType = "text/plain; charset=UTF-8"; given().port(tomcat.getPort()) - .filter(documentationConfiguration(this.restDocumentation)) + .filter(documentationConfiguration(restDocumentation)) .filter(document("curl-snippet-with-cookies")) .accept("application/json") .contentType(contentType) @@ -138,9 +137,9 @@ public void curlSnippetWithCookies() { } @Test - public void curlSnippetWithEmptyParameterQueryString() { + void curlSnippetWithEmptyParameterQueryString(RestDocumentationContextProvider restDocumentation) { given().port(tomcat.getPort()) - .filter(documentationConfiguration(this.restDocumentation)) + .filter(documentationConfiguration(restDocumentation)) .filter(document("curl-snippet-with-empty-parameter-query-string")) .accept("application/json") .param("a", "") @@ -155,9 +154,9 @@ public void curlSnippetWithEmptyParameterQueryString() { } @Test - public void curlSnippetWithQueryStringOnPost() { + void curlSnippetWithQueryStringOnPost(RestDocumentationContextProvider restDocumentation) { given().port(tomcat.getPort()) - .filter(documentationConfiguration(this.restDocumentation)) + .filter(documentationConfiguration(restDocumentation)) .filter(document("curl-snippet-with-query-string")) .accept("application/json") .param("foo", "bar") @@ -174,9 +173,9 @@ public void curlSnippetWithQueryStringOnPost() { } @Test - public void linksSnippet() { + void linksSnippet(RestDocumentationContextProvider restDocumentation) { given().port(tomcat.getPort()) - .filter(documentationConfiguration(this.restDocumentation)) + .filter(documentationConfiguration(restDocumentation)) .filter(document("links", links(linkWithRel("rel").description("The description")))) .accept("application/json") .get("/") @@ -187,9 +186,9 @@ public void linksSnippet() { } @Test - public void pathParametersSnippet() { + void pathParametersSnippet(RestDocumentationContextProvider restDocumentation) { given().port(tomcat.getPort()) - .filter(documentationConfiguration(this.restDocumentation)) + .filter(documentationConfiguration(restDocumentation)) .filter(document("path-parameters", pathParameters(parameterWithName("foo").description("The description")))) .accept("application/json") @@ -201,9 +200,9 @@ public void pathParametersSnippet() { } @Test - public void queryParametersSnippet() { + void queryParametersSnippet(RestDocumentationContextProvider restDocumentation) { given().port(tomcat.getPort()) - .filter(documentationConfiguration(this.restDocumentation)) + .filter(documentationConfiguration(restDocumentation)) .filter(document("query-parameters", queryParameters(parameterWithName("foo").description("The description")))) .accept("application/json") @@ -216,9 +215,9 @@ public void queryParametersSnippet() { } @Test - public void requestFieldsSnippet() { + void requestFieldsSnippet(RestDocumentationContextProvider restDocumentation) { given().port(tomcat.getPort()) - .filter(documentationConfiguration(this.restDocumentation)) + .filter(documentationConfiguration(restDocumentation)) .filter(document("request-fields", requestFields(fieldWithPath("a").description("The description")))) .accept("application/json") .body("{\"a\":\"alpha\"}") @@ -230,9 +229,9 @@ public void requestFieldsSnippet() { } @Test - public void requestPartsSnippet() { + void requestPartsSnippet(RestDocumentationContextProvider restDocumentation) { given().port(tomcat.getPort()) - .filter(documentationConfiguration(this.restDocumentation)) + .filter(documentationConfiguration(restDocumentation)) .filter(document("request-parts", requestParts(partWithName("a").description("The description")))) .multiPart("a", "foo") .post("/upload") @@ -243,9 +242,9 @@ public void requestPartsSnippet() { } @Test - public void responseFieldsSnippet() { + void responseFieldsSnippet(RestDocumentationContextProvider restDocumentation) { given().port(tomcat.getPort()) - .filter(documentationConfiguration(this.restDocumentation)) + .filter(documentationConfiguration(restDocumentation)) .filter(document("response-fields", responseFields(fieldWithPath("a").description("The description"), subsectionWithPath("links").description("Links to other resources")))) @@ -258,9 +257,9 @@ public void responseFieldsSnippet() { } @Test - public void parameterizedOutputDirectory() { + void parameterizedOutputDirectory(RestDocumentationContextProvider restDocumentation) { given().port(tomcat.getPort()) - .filter(documentationConfiguration(this.restDocumentation)) + .filter(documentationConfiguration(restDocumentation)) .filter(document("{method-name}")) .get("/") .then() @@ -270,9 +269,9 @@ public void parameterizedOutputDirectory() { } @Test - public void multiStep() { + void multiStep(RestDocumentationContextProvider restDocumentation) { RequestSpecification spec = new RequestSpecBuilder().setPort(tomcat.getPort()) - .addFilter(documentationConfiguration(this.restDocumentation)) + .addFilter(documentationConfiguration(restDocumentation)) .addFilter(document("{method-name}-{step}")) .build(); given(spec).get("/").then().statusCode(200); @@ -287,10 +286,10 @@ public void multiStep() { } @Test - public void additionalSnippets() { + void additionalSnippets(RestDocumentationContextProvider restDocumentation) { RestDocumentationFilter documentation = document("{method-name}-{step}"); RequestSpecification spec = new RequestSpecBuilder().setPort(tomcat.getPort()) - .addFilter(documentationConfiguration(this.restDocumentation)) + .addFilter(documentationConfiguration(restDocumentation)) .addFilter(documentation) .build(); given(spec) @@ -304,9 +303,9 @@ public void additionalSnippets() { } @Test - public void responseWithCookie() { + void responseWithCookie(RestDocumentationContextProvider restDocumentation) { given().port(tomcat.getPort()) - .filter(documentationConfiguration(this.restDocumentation)) + .filter(documentationConfiguration(restDocumentation)) .filter(document("set-cookie", preprocessResponse(modifyHeaders().remove(HttpHeaders.DATE).remove(HttpHeaders.CONTENT_TYPE)))) .get("/set-cookie") @@ -322,10 +321,10 @@ public void responseWithCookie() { } @Test - public void preprocessedRequest() { + void preprocessedRequest(RestDocumentationContextProvider restDocumentation) { Pattern pattern = Pattern.compile("(\"alpha\")"); given().port(tomcat.getPort()) - .filter(documentationConfiguration(this.restDocumentation)) + .filter(documentationConfiguration(restDocumentation)) .header("a", "alpha") .header("b", "bravo") .contentType("application/json") @@ -356,10 +355,10 @@ public void preprocessedRequest() { } @Test - public void defaultPreprocessedRequest() { + void defaultPreprocessedRequest(RestDocumentationContextProvider restDocumentation) { Pattern pattern = Pattern.compile("(\"alpha\")"); given().port(tomcat.getPort()) - .filter(documentationConfiguration(this.restDocumentation).operationPreprocessors() + .filter(documentationConfiguration(restDocumentation).operationPreprocessors() .withRequestDefaults(prettyPrint(), replacePattern(pattern, "\"<>\""), modifyUris().removePort(), modifyHeaders().remove("a").remove(HttpHeaders.CONTENT_LENGTH))) .header("a", "alpha") @@ -381,10 +380,10 @@ public void defaultPreprocessedRequest() { } @Test - public void preprocessedResponse() { + void preprocessedResponse(RestDocumentationContextProvider restDocumentation) { Pattern pattern = Pattern.compile("(\"alpha\")"); given().port(tomcat.getPort()) - .filter(documentationConfiguration(this.restDocumentation)) + .filter(documentationConfiguration(restDocumentation)) .filter(document("original-response")) .filter(document("preprocessed-response", preprocessResponse(prettyPrint(), maskLinks(), @@ -407,10 +406,10 @@ public void preprocessedResponse() { } @Test - public void defaultPreprocessedResponse() { + void defaultPreprocessedResponse(RestDocumentationContextProvider restDocumentation) { Pattern pattern = Pattern.compile("(\"alpha\")"); given().port(tomcat.getPort()) - .filter(documentationConfiguration(this.restDocumentation).operationPreprocessors() + .filter(documentationConfiguration(restDocumentation).operationPreprocessors() .withResponseDefaults(prettyPrint(), maskLinks(), modifyHeaders().remove("a").remove("Transfer-Encoding").remove("Date").remove("Server"), replacePattern(pattern, "\"<>\""), @@ -432,7 +431,7 @@ public void defaultPreprocessedResponse() { } @Test - public void customSnippetTemplate() throws MalformedURLException { + void customSnippetTemplate(RestDocumentationContextProvider restDocumentation) throws MalformedURLException { ClassLoader classLoader = new URLClassLoader( new URL[] { new File("src/test/resources/custom-snippet-templates").toURI().toURL() }, getClass().getClassLoader()); @@ -441,7 +440,7 @@ public void customSnippetTemplate() throws MalformedURLException { try { given().port(tomcat.getPort()) .accept("application/json") - .filter(documentationConfiguration(this.restDocumentation)) + .filter(documentationConfiguration(restDocumentation)) .filter(document("custom-snippet-template")) .get("/") .then() @@ -455,7 +454,7 @@ public void customSnippetTemplate() throws MalformedURLException { } @Test - public void exceptionShouldBeThrownWhenCallDocumentRequestSpecificationNotConfigured() { + void exceptionShouldBeThrownWhenCallDocumentRequestSpecificationNotConfigured() { assertThatThrownBy(() -> given().port(tomcat.getPort()).filter(document("default")).get("/")) .isInstanceOf(IllegalStateException.class) .hasMessage("REST Docs configuration not found. Did you forget to add a " @@ -463,7 +462,7 @@ public void exceptionShouldBeThrownWhenCallDocumentRequestSpecificationNotConfig } @Test - public void exceptionShouldBeThrownWhenCallDocumentSnippetsRequestSpecificationNotConfigured() { + void exceptionShouldBeThrownWhenCallDocumentSnippetsRequestSpecificationNotConfigured() { RestDocumentationFilter documentation = document("{method-name}-{step}"); assertThatThrownBy(() -> given().port(tomcat.getPort()) .filter(documentation.document(responseHeaders(headerWithName("a").description("one")))) diff --git a/spring-restdocs-restassured/src/test/java/org/springframework/restdocs/restassured/TomcatServer.java b/spring-restdocs-restassured/src/test/java/org/springframework/restdocs/restassured/TomcatServer.java index e2ddca89..8852ed98 100644 --- a/spring-restdocs-restassured/src/test/java/org/springframework/restdocs/restassured/TomcatServer.java +++ b/spring-restdocs-restassured/src/test/java/org/springframework/restdocs/restassured/TomcatServer.java @@ -22,8 +22,6 @@ import java.util.HashMap; import java.util.Map; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; import jakarta.servlet.ServletException; import jakarta.servlet.http.Cookie; import jakarta.servlet.http.HttpServlet; @@ -32,46 +30,58 @@ import org.apache.catalina.Context; import org.apache.catalina.LifecycleException; import org.apache.catalina.startup.Tomcat; -import org.junit.rules.ExternalResource; +import org.junit.jupiter.api.extension.AfterAllCallback; +import org.junit.jupiter.api.extension.BeforeAllCallback; +import org.junit.jupiter.api.extension.Extension; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.ExtensionContext.Namespace; +import org.junit.jupiter.api.extension.ExtensionContext.Store; +import tools.jackson.databind.ObjectMapper; import org.springframework.http.MediaType; import org.springframework.util.FileCopyUtils; /** - * {@link ExternalResource} that starts and stops a Tomcat server. + * {@link Extension} that starts and stops a Tomcat server. * * @author Andy Wilkinson */ -class TomcatServer extends ExternalResource { - - private Tomcat tomcat; +class TomcatServer implements BeforeAllCallback, AfterAllCallback { private int port; @Override - protected void before() throws LifecycleException { - this.tomcat = new Tomcat(); - this.tomcat.getConnector().setPort(0); - Context context = this.tomcat.addContext("/", null); - this.tomcat.addServlet("/", "test", new TestServlet()); - context.addServletMappingDecoded("/", "test"); - this.tomcat.addServlet("/", "set-cookie", new CookiesServlet()); - context.addServletMappingDecoded("/set-cookie", "set-cookie"); - this.tomcat.addServlet("/", "query-parameter", new QueryParameterServlet()); - context.addServletMappingDecoded("/query-parameter", "query-parameter"); - this.tomcat.addServlet("/", "form-url-encoded", new FormUrlEncodedServlet()); - context.addServletMappingDecoded("/form-url-encoded", "form-url-encoded"); - this.tomcat.start(); - this.port = this.tomcat.getConnector().getLocalPort(); + public void beforeAll(ExtensionContext extensionContext) { + Store store = extensionContext.getStore(Namespace.create(TomcatServer.class)); + store.getOrComputeIfAbsent(Tomcat.class, (key) -> { + Tomcat tomcat = new Tomcat(); + tomcat.getConnector().setPort(0); + Context context = tomcat.addContext("/", null); + tomcat.addServlet("/", "test", new TestServlet()); + context.addServletMappingDecoded("/", "test"); + tomcat.addServlet("/", "set-cookie", new CookiesServlet()); + context.addServletMappingDecoded("/set-cookie", "set-cookie"); + tomcat.addServlet("/", "query-parameter", new QueryParameterServlet()); + context.addServletMappingDecoded("/query-parameter", "query-parameter"); + tomcat.addServlet("/", "form-url-encoded", new FormUrlEncodedServlet()); + context.addServletMappingDecoded("/form-url-encoded", "form-url-encoded"); + try { + tomcat.start(); + } + catch (Exception ex) { + throw new RuntimeException(ex); + } + this.port = tomcat.getConnector().getLocalPort(); + return tomcat; + }); } @Override - protected void after() { - try { - this.tomcat.stop(); - } - catch (LifecycleException ex) { - throw new RuntimeException(ex); + public void afterAll(ExtensionContext extensionContext) throws LifecycleException { + Store store = extensionContext.getStore(Namespace.create(TomcatServer.class)); + Tomcat tomcat = store.get(Tomcat.class, Tomcat.class); + if (tomcat != null) { + tomcat.stop(); } } @@ -96,7 +106,7 @@ protected void doPost(HttpServletRequest request, HttpServletResponse response) respondWithJson(response); } - private void respondWithJson(HttpServletResponse response) throws IOException, JsonProcessingException { + private void respondWithJson(HttpServletResponse response) throws IOException { response.setCharacterEncoding("UTF-8"); response.setContentType("application/json"); Map content = new HashMap<>(); diff --git a/spring-restdocs-webtestclient/build.gradle b/spring-restdocs-webtestclient/build.gradle index d09bd819..7664569a 100644 --- a/spring-restdocs-webtestclient/build.gradle +++ b/spring-restdocs-webtestclient/build.gradle @@ -1,5 +1,5 @@ plugins { - id "io.spring.compatibility-test" version "0.0.4" + id 'org.springframework.restdocs.conventions' id "java-library" id "maven-publish" } @@ -11,20 +11,12 @@ dependencies { api("org.springframework:spring-test") api("org.springframework:spring-webflux") - internal(platform(project(":spring-restdocs-platform"))) + compileOnly("org.hamcrest:hamcrest-core") + compileOnly("com.google.code.findbugs:jsr305") + + testCompileOnly("org.hamcrest:hamcrest-core") testImplementation(testFixtures(project(":spring-restdocs-core"))) - testImplementation("junit:junit") - testImplementation("org.assertj:assertj-core") - testImplementation("org.hamcrest:hamcrest-library") - testImplementation("org.mockito:mockito-core") testRuntimeOnly("org.springframework:spring-context") } - -compatibilityTest { - dependency("Spring Framework") { springFramework -> - springFramework.groupId = "org.springframework" - springFramework.versions = ["6.0.+", "6.1.+"] - } -} diff --git a/spring-restdocs-webtestclient/src/main/java/org/springframework/restdocs/webtestclient/WebTestClientRequestConverter.java b/spring-restdocs-webtestclient/src/main/java/org/springframework/restdocs/webtestclient/WebTestClientRequestConverter.java index 7a07208f..3bb8cfa2 100644 --- a/spring-restdocs-webtestclient/src/main/java/org/springframework/restdocs/webtestclient/WebTestClientRequestConverter.java +++ b/spring-restdocs-webtestclient/src/main/java/org/springframework/restdocs/webtestclient/WebTestClientRequestConverter.java @@ -22,6 +22,7 @@ import java.util.List; import java.util.stream.Collectors; +import org.jspecify.annotations.Nullable; import reactor.core.publisher.Flux; import org.springframework.core.ResolvableType; @@ -68,17 +69,17 @@ private HttpHeaders extractRequestHeaders(ExchangeResult result) { return extracted; } - private List extractRequestParts(ExchangeResult result) { + private @Nullable List extractRequestParts(ExchangeResult result) { HttpMessageReader partHttpMessageReader = new DefaultPartHttpMessageReader(); return new MultipartHttpMessageReader(partHttpMessageReader) .readMono(ResolvableType.forClass(Part.class), new ExchangeResultReactiveHttpInputMessage(result), Collections.emptyMap()) .onErrorReturn(new LinkedMultiValueMap<>()) - .block() - .values() - .stream() - .flatMap((parts) -> parts.stream().map(this::createOperationRequestPart)) - .collect(Collectors.toList()); + .map((partsMap) -> partsMap.values() + .stream() + .flatMap((parts) -> parts.stream().map(this::createOperationRequestPart)) + .collect(Collectors.toList())) + .block(); } private OperationRequestPart createOperationRequestPart(Part part) { @@ -124,6 +125,9 @@ public HttpHeaders getHeaders() { @Override public Flux getBody() { byte[] requestBodyContent = this.result.getRequestBodyContent(); + if (requestBodyContent == null) { + requestBodyContent = new byte[0]; + } DefaultDataBuffer buffer = new DefaultDataBufferFactory().allocateBuffer(requestBodyContent.length); buffer.write(requestBodyContent); return Flux.fromArray(new DataBuffer[] { buffer }); diff --git a/spring-restdocs-webtestclient/src/main/java/org/springframework/restdocs/webtestclient/WebTestClientResponseConverter.java b/spring-restdocs-webtestclient/src/main/java/org/springframework/restdocs/webtestclient/WebTestClientResponseConverter.java index a6d8891b..312ecfb1 100644 --- a/spring-restdocs-webtestclient/src/main/java/org/springframework/restdocs/webtestclient/WebTestClientResponseConverter.java +++ b/spring-restdocs-webtestclient/src/main/java/org/springframework/restdocs/webtestclient/WebTestClientResponseConverter.java @@ -20,6 +20,8 @@ import java.util.List; import java.util.stream.Collectors; +import org.jspecify.annotations.Nullable; + import org.springframework.http.HttpHeaders; import org.springframework.restdocs.operation.OperationResponse; import org.springframework.restdocs.operation.OperationResponseFactory; @@ -46,7 +48,7 @@ public OperationResponse convert(ExchangeResult result) { private HttpHeaders extractHeaders(ExchangeResult result) { HttpHeaders headers = result.getResponseHeaders(); - if (result.getResponseCookies().isEmpty() || headers.containsKey(HttpHeaders.SET_COOKIE)) { + if (result.getResponseCookies().isEmpty() || headers.containsHeader(HttpHeaders.SET_COOKIE)) { return headers; } result.getResponseCookies() @@ -97,7 +99,7 @@ private ResponseCookie createResponseCookie(org.springframework.http.ResponseCoo return new ResponseCookie(original.getName(), original.getValue()); } - private void appendIfAvailable(StringBuilder header, String name, String value) { + private void appendIfAvailable(StringBuilder header, String name, @Nullable String value) { if (StringUtils.hasText(value)) { header.append(name); header.append(value); diff --git a/spring-restdocs-webtestclient/src/main/java/org/springframework/restdocs/webtestclient/package-info.java b/spring-restdocs-webtestclient/src/main/java/org/springframework/restdocs/webtestclient/package-info.java index d2b02ea0..aed392e2 100644 --- a/spring-restdocs-webtestclient/src/main/java/org/springframework/restdocs/webtestclient/package-info.java +++ b/spring-restdocs-webtestclient/src/main/java/org/springframework/restdocs/webtestclient/package-info.java @@ -17,4 +17,7 @@ /** * Core classes for using Spring REST Docs with Spring Framework's WebTestClient. */ +@NullMarked package org.springframework.restdocs.webtestclient; + +import org.jspecify.annotations.NullMarked; diff --git a/spring-restdocs-webtestclient/src/test/java/org/springframework/restdocs/webtestclient/WebTestClientRequestConverterTests.java b/spring-restdocs-webtestclient/src/test/java/org/springframework/restdocs/webtestclient/WebTestClientRequestConverterTests.java index 487723dc..2788f181 100644 --- a/spring-restdocs-webtestclient/src/test/java/org/springframework/restdocs/webtestclient/WebTestClientRequestConverterTests.java +++ b/spring-restdocs-webtestclient/src/test/java/org/springframework/restdocs/webtestclient/WebTestClientRequestConverterTests.java @@ -20,7 +20,7 @@ import java.nio.charset.StandardCharsets; import java.util.Arrays; -import org.junit.Test; +import org.junit.jupiter.api.Test; import org.springframework.core.io.ByteArrayResource; import org.springframework.http.ContentDisposition; @@ -39,6 +39,7 @@ import org.springframework.web.reactive.function.server.ServerResponse; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.entry; import static org.springframework.web.reactive.function.server.RequestPredicates.GET; import static org.springframework.web.reactive.function.server.RequestPredicates.POST; @@ -47,12 +48,12 @@ * * @author Andy Wilkinson */ -public class WebTestClientRequestConverterTests { +class WebTestClientRequestConverterTests { private final WebTestClientRequestConverter converter = new WebTestClientRequestConverter(); @Test - public void httpRequest() { + void httpRequest() { ExchangeResult result = WebTestClient.bindToRouterFunction(RouterFunctions.route(GET("/foo"), (req) -> null)) .configureClient() .baseUrl("http://localhost") @@ -68,7 +69,7 @@ public void httpRequest() { } @Test - public void httpRequestWithCustomPort() { + void httpRequestWithCustomPort() { ExchangeResult result = WebTestClient.bindToRouterFunction(RouterFunctions.route(GET("/foo"), (req) -> null)) .configureClient() .baseUrl("http://localhost:8080") @@ -84,7 +85,7 @@ public void httpRequestWithCustomPort() { } @Test - public void requestWithHeaders() { + void requestWithHeaders() { ExchangeResult result = WebTestClient.bindToRouterFunction(RouterFunctions.route(GET("/"), (req) -> null)) .configureClient() .baseUrl("http://localhost") @@ -99,12 +100,12 @@ public void requestWithHeaders() { OperationRequest request = this.converter.convert(result); assertThat(request.getUri()).isEqualTo(URI.create("http://localhost/foo")); assertThat(request.getMethod()).isEqualTo(HttpMethod.GET); - assertThat(request.getHeaders()).containsEntry("a", Arrays.asList("alpha", "apple")); - assertThat(request.getHeaders()).containsEntry("b", Arrays.asList("bravo")); + assertThat(request.getHeaders().headerSet()).contains(entry("a", Arrays.asList("alpha", "apple")), + entry("b", Arrays.asList("bravo"))); } @Test - public void httpsRequest() { + void httpsRequest() { ExchangeResult result = WebTestClient.bindToRouterFunction(RouterFunctions.route(GET("/foo"), (req) -> null)) .configureClient() .baseUrl("https://localhost") @@ -120,7 +121,7 @@ public void httpsRequest() { } @Test - public void httpsRequestWithCustomPort() { + void httpsRequestWithCustomPort() { ExchangeResult result = WebTestClient.bindToRouterFunction(RouterFunctions.route(GET("/foo"), (req) -> null)) .configureClient() .baseUrl("https://localhost:8443") @@ -136,7 +137,7 @@ public void httpsRequestWithCustomPort() { } @Test - public void getRequestWithQueryString() { + void getRequestWithQueryString() { ExchangeResult result = WebTestClient.bindToRouterFunction(RouterFunctions.route(GET("/foo"), (req) -> null)) .configureClient() .baseUrl("http://localhost") @@ -152,7 +153,7 @@ public void getRequestWithQueryString() { } @Test - public void postRequestWithFormDataParameters() { + void postRequestWithFormDataParameters() { MultiValueMap parameters = new LinkedMultiValueMap<>(); parameters.addAll("a", Arrays.asList("alpha", "apple")); parameters.addAll("b", Arrays.asList("br&vo")); @@ -180,7 +181,7 @@ public void postRequestWithFormDataParameters() { } @Test - public void postRequestWithQueryStringParameters() { + void postRequestWithQueryStringParameters() { ExchangeResult result = WebTestClient.bindToRouterFunction(RouterFunctions.route(POST("/foo"), (req) -> { req.body(BodyExtractors.toFormData()).block(); return null; @@ -199,7 +200,7 @@ public void postRequestWithQueryStringParameters() { } @Test - public void postRequestWithQueryStringAndFormDataParameters() { + void postRequestWithQueryStringAndFormDataParameters() { MultiValueMap parameters = new LinkedMultiValueMap<>(); parameters.addAll("a", Arrays.asList("apple")); ExchangeResult result = WebTestClient.bindToRouterFunction(RouterFunctions.route(POST("/foo"), (req) -> { @@ -226,7 +227,7 @@ public void postRequestWithQueryStringAndFormDataParameters() { } @Test - public void postRequestWithNoContentType() { + void postRequestWithNoContentType() { ExchangeResult result = WebTestClient .bindToRouterFunction(RouterFunctions.route(POST("/foo"), (req) -> ServerResponse.ok().build())) .configureClient() @@ -243,7 +244,7 @@ public void postRequestWithNoContentType() { } @Test - public void multipartUpload() { + void multipartUpload() { MultiValueMap multipartData = new LinkedMultiValueMap<>(); multipartData.add("file", new byte[] { 1, 2, 3, 4 }); ExchangeResult result = WebTestClient @@ -266,14 +267,14 @@ public void multipartUpload() { OperationRequestPart part = request.getParts().iterator().next(); assertThat(part.getName()).isEqualTo("file"); assertThat(part.getSubmittedFileName()).isNull(); - assertThat(part.getHeaders()).hasSize(2); + assertThat(part.getHeaders().size()).isEqualTo(2); assertThat(part.getHeaders().getContentLength()).isEqualTo(4L); assertThat(part.getHeaders().getContentDisposition().getName()).isEqualTo("file"); assertThat(part.getContent()).containsExactly(1, 2, 3, 4); } @Test - public void multipartUploadFromResource() { + void multipartUploadFromResource() { MultiValueMap multipartData = new LinkedMultiValueMap<>(); multipartData.add("file", new ByteArrayResource(new byte[] { 1, 2, 3, 4 }) { @@ -303,7 +304,7 @@ public String getFilename() { OperationRequestPart part = request.getParts().iterator().next(); assertThat(part.getName()).isEqualTo("file"); assertThat(part.getSubmittedFileName()).isEqualTo("image.png"); - assertThat(part.getHeaders()).hasSize(3); + assertThat(part.getHeaders().size()).isEqualTo(3); assertThat(part.getHeaders().getContentLength()).isEqualTo(4); ContentDisposition contentDisposition = part.getHeaders().getContentDisposition(); assertThat(contentDisposition.getName()).isEqualTo("file"); @@ -313,7 +314,7 @@ public String getFilename() { } @Test - public void requestWithCookies() { + void requestWithCookies() { ExchangeResult result = WebTestClient.bindToRouterFunction(RouterFunctions.route(GET("/foo"), (req) -> null)) .configureClient() .baseUrl("http://localhost") diff --git a/spring-restdocs-webtestclient/src/test/java/org/springframework/restdocs/webtestclient/WebTestClientResponseConverterTests.java b/spring-restdocs-webtestclient/src/test/java/org/springframework/restdocs/webtestclient/WebTestClientResponseConverterTests.java index 589e79dd..84582d98 100644 --- a/spring-restdocs-webtestclient/src/test/java/org/springframework/restdocs/webtestclient/WebTestClientResponseConverterTests.java +++ b/spring-restdocs-webtestclient/src/test/java/org/springframework/restdocs/webtestclient/WebTestClientResponseConverterTests.java @@ -18,7 +18,7 @@ import java.util.Collections; -import org.junit.Test; +import org.junit.jupiter.api.Test; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; @@ -32,6 +32,7 @@ import org.springframework.web.reactive.function.server.ServerResponse; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.entry; import static org.springframework.web.reactive.function.server.RequestPredicates.GET; /** @@ -39,12 +40,12 @@ * * @author Andy Wilkinson */ -public class WebTestClientResponseConverterTests { +class WebTestClientResponseConverterTests { private final WebTestClientResponseConverter converter = new WebTestClientResponseConverter(); @Test - public void basicResponse() { + void basicResponse() { ExchangeResult result = WebTestClient .bindToRouterFunction( RouterFunctions.route(GET("/foo"), (req) -> ServerResponse.ok().bodyValue("Hello, World!"))) @@ -65,7 +66,7 @@ public void basicResponse() { } @Test - public void responseWithCookie() { + void responseWithCookie() { ExchangeResult result = WebTestClient .bindToRouterFunction(RouterFunctions.route(GET("/foo"), (req) -> ServerResponse.ok() @@ -83,16 +84,15 @@ public void responseWithCookie() { .expectBody() .returnResult(); OperationResponse response = this.converter.convert(result); - assertThat(response.getHeaders()).hasSize(1); - assertThat(response.getHeaders()).containsEntry(HttpHeaders.SET_COOKIE, - Collections.singletonList("name=value; Domain=localhost; HttpOnly")); + assertThat(response.getHeaders().headerSet()).containsOnly( + entry(HttpHeaders.SET_COOKIE, Collections.singletonList("name=value; Domain=localhost; HttpOnly"))); assertThat(response.getCookies()).hasSize(1); assertThat(response.getCookies()).first().extracting(ResponseCookie::getName).isEqualTo("name"); assertThat(response.getCookies()).first().extracting(ResponseCookie::getValue).isEqualTo("value"); } @Test - public void responseWithNonStandardStatusCode() { + void responseWithNonStandardStatusCode() { ExchangeResult result = WebTestClient .bindToRouterFunction(RouterFunctions.route(GET("/foo"), (req) -> ServerResponse.status(210).build())) .configureClient() diff --git a/spring-restdocs-webtestclient/src/test/java/org/springframework/restdocs/webtestclient/WebTestClientRestDocumentationConfigurerTests.java b/spring-restdocs-webtestclient/src/test/java/org/springframework/restdocs/webtestclient/WebTestClientRestDocumentationConfigurerTests.java index 526fc4ae..e18d991c 100644 --- a/spring-restdocs-webtestclient/src/test/java/org/springframework/restdocs/webtestclient/WebTestClientRestDocumentationConfigurerTests.java +++ b/spring-restdocs-webtestclient/src/test/java/org/springframework/restdocs/webtestclient/WebTestClientRestDocumentationConfigurerTests.java @@ -18,12 +18,14 @@ import java.net.URI; -import org.junit.Rule; -import org.junit.Test; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.ArgumentCaptor; import org.springframework.http.HttpMethod; -import org.springframework.restdocs.JUnitRestDocumentation; +import org.springframework.restdocs.RestDocumentationContextProvider; +import org.springframework.restdocs.RestDocumentationExtension; import org.springframework.test.web.reactive.server.WebTestClient; import org.springframework.web.reactive.function.client.ClientRequest; import org.springframework.web.reactive.function.client.ExchangeFunction; @@ -38,16 +40,19 @@ * * @author Andy Wilkinson */ -public class WebTestClientRestDocumentationConfigurerTests { +@ExtendWith(RestDocumentationExtension.class) +class WebTestClientRestDocumentationConfigurerTests { - @Rule - public final JUnitRestDocumentation restDocumentation = new JUnitRestDocumentation(); + private WebTestClientRestDocumentationConfigurer configurer; - private final WebTestClientRestDocumentationConfigurer configurer = new WebTestClientRestDocumentationConfigurer( - this.restDocumentation); + @BeforeEach + void setUp(RestDocumentationContextProvider restDocumentation) { + this.configurer = new WebTestClientRestDocumentationConfigurer(restDocumentation); + + } @Test - public void configurationCanBeRetrievedButOnlyOnce() { + void configurationCanBeRetrievedButOnlyOnce() { ClientRequest request = ClientRequest.create(HttpMethod.GET, URI.create("/test")) .header(WebTestClient.WEBTESTCLIENT_REQUEST_ID, "1") .build(); @@ -58,7 +63,7 @@ public void configurationCanBeRetrievedButOnlyOnce() { } @Test - public void requestUriHasDefaultsAppliedWhenItHasNoHost() { + void requestUriHasDefaultsAppliedWhenItHasNoHost() { ClientRequest request = ClientRequest.create(HttpMethod.GET, URI.create("/test?foo=bar#baz")) .header(WebTestClient.WEBTESTCLIENT_REQUEST_ID, "1") .build(); @@ -70,7 +75,7 @@ public void requestUriHasDefaultsAppliedWhenItHasNoHost() { } @Test - public void requestUriIsNotChangedWhenItHasAHost() { + void requestUriIsNotChangedWhenItHasAHost() { ClientRequest request = ClientRequest .create(HttpMethod.GET, URI.create("https://api.example.com:4567/test?foo=bar#baz")) .header(WebTestClient.WEBTESTCLIENT_REQUEST_ID, "1") diff --git a/spring-restdocs-webtestclient/src/test/java/org/springframework/restdocs/webtestclient/WebTestClientRestDocumentationIntegrationTests.java b/spring-restdocs-webtestclient/src/test/java/org/springframework/restdocs/webtestclient/WebTestClientRestDocumentationIntegrationTests.java index 711c8a4d..38a58d3b 100644 --- a/spring-restdocs-webtestclient/src/test/java/org/springframework/restdocs/webtestclient/WebTestClientRestDocumentationIntegrationTests.java +++ b/spring-restdocs-webtestclient/src/test/java/org/springframework/restdocs/webtestclient/WebTestClientRestDocumentationIntegrationTests.java @@ -30,15 +30,16 @@ import java.util.stream.Stream; import org.assertj.core.api.Condition; -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseCookie; -import org.springframework.restdocs.JUnitRestDocumentation; +import org.springframework.restdocs.RestDocumentationContextProvider; +import org.springframework.restdocs.RestDocumentationExtension; import org.springframework.restdocs.templates.TemplateFormat; import org.springframework.restdocs.templates.TemplateFormats; import org.springframework.restdocs.testfixtures.SnippetConditions; @@ -75,15 +76,13 @@ * * @author Andy Wilkinson */ +@ExtendWith(RestDocumentationExtension.class) public class WebTestClientRestDocumentationIntegrationTests { - @Rule - public final JUnitRestDocumentation restDocumentation = new JUnitRestDocumentation(); - private WebTestClient webTestClient; - @Before - public void setUp() { + @BeforeEach + void setUp(RestDocumentationContextProvider restDocumentation) { RouterFunction route = RouterFunctions .route(RequestPredicates.GET("/"), (request) -> ServerResponse.status(HttpStatus.OK).body(fromValue(new Person("Jane", "Doe")))) @@ -99,12 +98,12 @@ public void setUp() { this.webTestClient = WebTestClient.bindToRouterFunction(route) .configureClient() .baseUrl("https://api.example.com") - .filter(documentationConfiguration(this.restDocumentation)) + .filter(documentationConfiguration(restDocumentation)) .build(); } @Test - public void defaultSnippetGeneration() { + void defaultSnippetGeneration() { File outputDir = new File("build/generated-snippets/default-snippets"); FileSystemUtils.deleteRecursively(outputDir); this.webTestClient.get() @@ -119,7 +118,7 @@ public void defaultSnippetGeneration() { } @Test - public void pathParametersSnippet() { + void pathParametersSnippet() { this.webTestClient.get() .uri("/{foo}/{bar}", "1", "2") .exchange() @@ -136,7 +135,7 @@ public void pathParametersSnippet() { } @Test - public void queryParametersSnippet() { + void queryParametersSnippet() { this.webTestClient.get() .uri("/?a=alpha&b=bravo") .exchange() @@ -153,7 +152,7 @@ public void queryParametersSnippet() { } @Test - public void multipart() { + void multipart() { MultiValueMap multipartData = new LinkedMultiValueMap<>(); multipartData.add("a", "alpha"); multipartData.add("b", "bravo"); @@ -173,7 +172,7 @@ public void multipart() { } @Test - public void responseWithSetCookie() { + void responseWithSetCookie() { this.webTestClient.get() .uri("/set-cookie") .exchange() @@ -187,7 +186,7 @@ public void responseWithSetCookie() { } @Test - public void curlSnippetWithCookies() { + void curlSnippetWithCookies() { this.webTestClient.get() .uri("/") .cookie("cookieName", "cookieVal") @@ -204,7 +203,7 @@ public void curlSnippetWithCookies() { } @Test - public void curlSnippetWithEmptyParameterQueryString() { + void curlSnippetWithEmptyParameterQueryString() { this.webTestClient.get() .uri("/?a=") .accept(MediaType.APPLICATION_JSON) @@ -220,7 +219,7 @@ public void curlSnippetWithEmptyParameterQueryString() { } @Test - public void httpieSnippetWithCookies() { + void httpieSnippetWithCookies() { this.webTestClient.get() .uri("/") .cookie("cookieName", "cookieVal") @@ -237,7 +236,7 @@ public void httpieSnippetWithCookies() { } @Test - public void illegalStateExceptionShouldBeThrownWhenCallDocumentWebClientNotConfigured() { + void illegalStateExceptionShouldBeThrownWhenCallDocumentWebClientNotConfigured() { assertThatThrownBy(() -> this.webTestClient .mutateWith((builder, httpHandlerBuilder, connector) -> builder.filters(List::clear).build()) .get()