From 272ed24de8587a82b850f6c422d340cbdf7b77dd Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Fri, 13 Sep 2024 10:34:32 +0100 Subject: [PATCH] Use getUriTemplate of MockHttpServletRequest Fixes gh-939 --- .../docs/asciidoc/documenting-your-api.adoc | 2 +- .../MockMvcRestDocumentationConfigurer.java | 27 +++++++++++++-- ...ckMvcRestDocumentationConfigurerTests.java | 33 +++++++++++++++++++ 3 files changed, 59 insertions(+), 3 deletions(-) diff --git a/docs/src/docs/asciidoc/documenting-your-api.adoc b/docs/src/docs/asciidoc/documenting-your-api.adoc index 3f8dc5de..ce3dc590 100644 --- a/docs/src/docs/asciidoc/documenting-your-api.adoc +++ b/docs/src/docs/asciidoc/documenting-your-api.adoc @@ -802,7 +802,7 @@ 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, to make the path parameters available for documentation, you must build the request by using one of the methods on `RestDocumentationRequestBuilders` rather than `MockMvcRequestBuilders`. +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. 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 73eeb999..9c495ebb 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 @@ -1,5 +1,5 @@ /* - * Copyright 2014-2019 the original author or authors. + * Copyright 2014-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,8 +16,10 @@ package org.springframework.restdocs.mockmvc; +import java.lang.reflect.Method; import java.util.HashMap; import java.util.Map; +import java.util.function.Function; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.restdocs.RestDocumentationContext; @@ -27,6 +29,7 @@ import org.springframework.restdocs.generate.RestDocumentationGenerator; import org.springframework.test.web.servlet.request.RequestPostProcessor; import org.springframework.test.web.servlet.setup.ConfigurableMockMvcBuilder; import org.springframework.test.web.servlet.setup.MockMvcConfigurer; +import org.springframework.util.ReflectionUtils; import org.springframework.web.context.WebApplicationContext; /** @@ -85,6 +88,26 @@ public final class MockMvcRestDocumentationConfigurer extends private final class ConfigurerApplyingRequestPostProcessor implements RequestPostProcessor { + private static final Function urlTemplateExtractor; + + static { + Function fromRequestAttribute = ( + request) -> (String) request.getAttribute(RestDocumentationGenerator.ATTRIBUTE_NAME_URL_TEMPLATE); + Function extractor; + try { + Method accessorMethod = MockHttpServletRequest.class.getMethod("getUriTemplate"); + extractor = (request) -> { + String urlTemplate = fromRequestAttribute.apply(request); + return (urlTemplate != null) ? urlTemplate + : (String) ReflectionUtils.invokeMethod(accessorMethod, request); + }; + } + catch (Exception ex) { + extractor = fromRequestAttribute; + } + urlTemplateExtractor = extractor; + } + private final RestDocumentationContextProvider contextManager; private ConfigurerApplyingRequestPostProcessor(RestDocumentationContextProvider contextManager) { @@ -97,7 +120,7 @@ public final class MockMvcRestDocumentationConfigurer extends Map configuration = new HashMap<>(); configuration.put(MockHttpServletRequest.class.getName(), request); configuration.put(RestDocumentationGenerator.ATTRIBUTE_NAME_URL_TEMPLATE, - request.getAttribute(RestDocumentationGenerator.ATTRIBUTE_NAME_URL_TEMPLATE)); + urlTemplateExtractor.apply(request)); configuration.put(RestDocumentationContext.class.getName(), context); request.setAttribute(RestDocumentationResultHandler.ATTRIBUTE_NAME_CONFIGURATION, configuration); MockMvcRestDocumentationConfigurer.this.apply(configuration, context); 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 a135125c..a1e3e9a0 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 @@ -16,12 +16,18 @@ package org.springframework.restdocs.mockmvc; +import java.lang.reflect.Method; +import java.util.Map; + +import org.junit.Assume; import org.junit.Rule; import org.junit.Test; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.restdocs.JUnitRestDocumentation; +import org.springframework.restdocs.generate.RestDocumentationGenerator; import org.springframework.test.web.servlet.request.RequestPostProcessor; +import org.springframework.util.ReflectionUtils; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; import org.springframework.web.servlet.support.ServletUriComponentsBuilder; @@ -86,6 +92,33 @@ public class MockMvcRestDocumentationConfigurerTests { assertThat(this.request.getHeader("Content-Length")).isNull(); } + @Test + @SuppressWarnings("unchecked") + public void uriTemplateFromRequestAttribute() { + RequestPostProcessor postProcessor = new MockMvcRestDocumentationConfigurer(this.restDocumentation) + .beforeMockMvcCreated(null, null); + this.request.setAttribute(RestDocumentationGenerator.ATTRIBUTE_NAME_URL_TEMPLATE, "{a}/{b}"); + postProcessor.postProcessRequest(this.request); + Map configuration = (Map) this.request + .getAttribute(RestDocumentationResultHandler.ATTRIBUTE_NAME_CONFIGURATION); + assertThat(configuration).containsEntry(RestDocumentationGenerator.ATTRIBUTE_NAME_URL_TEMPLATE, "{a}/{b}"); + } + + @Test + @SuppressWarnings("unchecked") + public void uriTemplateFromRequest() { + Method setUriTemplate = ReflectionUtils.findMethod(MockHttpServletRequest.class, "setUriTemplate", + String.class); + Assume.assumeNotNull(setUriTemplate); + RequestPostProcessor postProcessor = new MockMvcRestDocumentationConfigurer(this.restDocumentation) + .beforeMockMvcCreated(null, null); + ReflectionUtils.invokeMethod(setUriTemplate, this.request, "{a}/{b}"); + postProcessor.postProcessRequest(this.request); + Map configuration = (Map) this.request + .getAttribute(RestDocumentationResultHandler.ATTRIBUTE_NAME_CONFIGURATION); + assertThat(configuration).containsEntry(RestDocumentationGenerator.ATTRIBUTE_NAME_URL_TEMPLATE, "{a}/{b}"); + } + private void assertUriConfiguration(String scheme, String host, int port) { assertThat(scheme).isEqualTo(this.request.getScheme()); assertThat(host).isEqualTo(this.request.getServerName());