From fd63c2885c2d1791ea4b35322b3098d011e54af0 Mon Sep 17 00:00:00 2001 From: christophejan <69955393+christophejan@users.noreply.github.com> Date: Wed, 27 Apr 2022 21:08:48 +0200 Subject: [PATCH 1/2] Ease group declaration through code or properties with actuators (Global actuator customizers) --- .../core/SpringDocConfiguration.java | 7 +- ...pringdocActuatorBeanFactoryConfigurer.java | 2 - .../ActuatorOpenApiCustomizer.java | 2 +- .../ActuatorOperationCustomizer.java | 2 +- .../api/app186/SpringDocApp186Test.java | 125 +++ .../src/test/resources/results/app186.json | 816 +++++++++++++++++ .../api/v30/app186/SpringDocApp186Test.java | 131 +++ .../test/resources/results/3.0.1/app186.json | 828 ++++++++++++++++++ 8 files changed, 1905 insertions(+), 8 deletions(-) create mode 100644 springdoc-openapi-webflux-core/src/test/java/test/org/springdoc/api/app186/SpringDocApp186Test.java create mode 100644 springdoc-openapi-webflux-core/src/test/resources/results/app186.json create mode 100644 springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/v30/app186/SpringDocApp186Test.java create mode 100644 springdoc-openapi-webmvc-core/src/test/resources/results/3.0.1/app186.json diff --git a/springdoc-openapi-common/src/main/java/org/springdoc/core/SpringDocConfiguration.java b/springdoc-openapi-common/src/main/java/org/springdoc/core/SpringDocConfiguration.java index 92bc36a9b..6a415637b 100644 --- a/springdoc-openapi-common/src/main/java/org/springdoc/core/SpringDocConfiguration.java +++ b/springdoc-openapi-common/src/main/java/org/springdoc/core/SpringDocConfiguration.java @@ -52,9 +52,8 @@ import org.springdoc.core.customizers.DataRestDelegatingMethodParameterCustomizer; import org.springdoc.core.customizers.DelegatingMethodParameterCustomizer; import org.springdoc.core.customizers.GlobalOpenApiCustomizer; +import org.springdoc.core.customizers.GlobalOperationCustomizer; import org.springdoc.core.customizers.OpenApiBuilderCustomizer; -import org.springdoc.core.customizers.OpenApiCustomiser; -import org.springdoc.core.customizers.OperationCustomizer; import org.springdoc.core.customizers.PropertyCustomizer; import org.springdoc.core.customizers.ServerBaseUrlCustomizer; import org.springdoc.core.providers.ActuatorProvider; @@ -462,7 +461,7 @@ static BeanFactoryPostProcessor springdocBeanFactoryPostProcessor3(List() { + @Override + public boolean equal(Object o1, Object o2) { + return true; + } + })); + + @SpringBootApplication + @ComponentScan(basePackages = { "org.springdoc", "test.org.springdoc.api.app186" }) + static class SpringDocTestApp { + + @Bean + public GroupedOpenApi asCodeCheckBackwardsCompatibility(OpenApiCustomiser actuatorOpenApiCustomiser, + OperationCustomizer actuatorCustomizer, WebEndpointProperties endpointProperties) { + return GroupedOpenApi.builder() + .group("group-actuator-as-code-check-backwards-compatibility") + .pathsToMatch(endpointProperties.getBasePath()+ ALL_PATTERN) + .addOpenApiCustomiser(actuatorOpenApiCustomiser) + .addOperationCustomizer(actuatorCustomizer) + .build(); + } + + @Bean + public GroupedOpenApi asCode(WebEndpointProperties endpointProperties) { + return GroupedOpenApi.builder() + .group("group-actuator-as-code") + .pathsToMatch(endpointProperties.getBasePath()+ ALL_PATTERN) + .build(); + } + } + + private void assertBodyApp186(String content) { + try { + JSONAssert.assertEquals(getContent("results/app186.json"), content, STRICT_IGNORING_OPERATION_ID); + } catch (JSONException e) { + throw new RuntimeException(e); + } + } + + @Test + public void testApp() throws Exception { + webTestClient.get().uri(Constants.DEFAULT_API_DOCS_URL).exchange() + .expectStatus().isOk() + .expectBody(String.class).value(this::assertBodyApp186); + } + + @Test + public void testGroupActuatorAsCodeCheckBackwardsCompatibility() throws Exception { + webTestClient.get().uri(Constants.DEFAULT_API_DOCS_URL + "/group-actuator-as-code-check-backwards-compatibility").exchange() + .expectStatus().isOk() + .expectBody(String.class).value(this::assertBodyApp186); + } + + @Test + public void testGroupActuatorAsCode() throws Exception { + webTestClient.get().uri(Constants.DEFAULT_API_DOCS_URL + "/group-actuator-as-code").exchange() + .expectStatus().isOk() + .expectBody(String.class).value(this::assertBodyApp186); + } + + @Test + public void testGroupActuatorAsProperties() throws Exception { + webTestClient.get().uri(Constants.DEFAULT_API_DOCS_URL + "/group-actuator-as-properties").exchange() + .expectStatus().isOk() + .expectBody(String.class).value(this::assertBodyApp186); + } + +} \ No newline at end of file diff --git a/springdoc-openapi-webflux-core/src/test/resources/results/app186.json b/springdoc-openapi-webflux-core/src/test/resources/results/app186.json new file mode 100644 index 000000000..02108d263 --- /dev/null +++ b/springdoc-openapi-webflux-core/src/test/resources/results/app186.json @@ -0,0 +1,816 @@ +{ + "openapi": "3.0.1", + "info": { + "title": "OpenAPI definition", + "version": "v0" + }, + "servers": [ + { + "url": "", + "description": "Generated server url" + } + ], + "tags": [ + { + "name": "Actuator", + "description": "Monitor and interact", + "externalDocs": { + "description": "Spring Boot Actuator Web API Documentation", + "url": "https://docs.spring.io/spring-boot/docs/current/actuator-api/html/" + } + } + ], + "paths": { + "/actuator/loggers/{name}": { + "get": { + "tags": [ + "Actuator" + ], + "operationId": "handle_1_6", + "parameters": [ + { + "name": "name", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/vnd.spring-boot.actuator.v3+json": { + "schema": { + "type": "object" + } + }, + "application/vnd.spring-boot.actuator.v2+json": { + "schema": { + "type": "object" + } + }, + "application/json": { + "schema": { + "type": "object" + } + } + } + } + } + }, + "post": { + "tags": [ + "Actuator" + ], + "operationId": "handle", + "parameters": [ + { + "name": "name", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "string", + "enum": [ + "TRACE", + "DEBUG", + "INFO", + "WARN", + "ERROR", + "FATAL", + "OFF" + ] + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "*/*": { + "schema": { + "type": "object" + } + } + } + } + } + } + }, + "/actuator/threaddump": { + "get": { + "tags": [ + "Actuator" + ], + "operationId": "handle_1_1", + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain;charset=UTF-8": { + "schema": { + "type": "object" + } + }, + "application/vnd.spring-boot.actuator.v3+json": { + "schema": { + "type": "object" + } + }, + "application/vnd.spring-boot.actuator.v2+json": { + "schema": { + "type": "object" + } + }, + "application/json": { + "schema": { + "type": "object" + } + } + } + } + } + } + }, + "/actuator/scheduledtasks": { + "get": { + "tags": [ + "Actuator" + ], + "operationId": "handle_1_2", + "responses": { + "200": { + "description": "OK", + "content": { + "application/vnd.spring-boot.actuator.v3+json": { + "schema": { + "type": "object" + } + }, + "application/vnd.spring-boot.actuator.v2+json": { + "schema": { + "type": "object" + } + }, + "application/json": { + "schema": { + "type": "object" + } + } + } + } + } + } + }, + "/actuator/metrics/{requiredMetricName}": { + "get": { + "tags": [ + "Actuator" + ], + "operationId": "handle_1_3", + "parameters": [ + { + "name": "requiredMetricName", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/vnd.spring-boot.actuator.v3+json": { + "schema": { + "type": "object" + } + }, + "application/vnd.spring-boot.actuator.v2+json": { + "schema": { + "type": "object" + } + }, + "application/json": { + "schema": { + "type": "object" + } + } + } + } + } + } + }, + "/actuator/metrics": { + "get": { + "tags": [ + "Actuator" + ], + "operationId": "handle_1_4", + "responses": { + "200": { + "description": "OK", + "content": { + "application/vnd.spring-boot.actuator.v3+json": { + "schema": { + "type": "object" + } + }, + "application/vnd.spring-boot.actuator.v2+json": { + "schema": { + "type": "object" + } + }, + "application/json": { + "schema": { + "type": "object" + } + } + } + } + } + } + }, + "/actuator/mappings": { + "get": { + "tags": [ + "Actuator" + ], + "operationId": "handle_1_5", + "responses": { + "200": { + "description": "OK", + "content": { + "application/vnd.spring-boot.actuator.v3+json": { + "schema": { + "type": "object" + } + }, + "application/vnd.spring-boot.actuator.v2+json": { + "schema": { + "type": "object" + } + }, + "application/json": { + "schema": { + "type": "object" + } + } + } + } + } + } + }, + "/actuator/loggers": { + "get": { + "tags": [ + "Actuator" + ], + "operationId": "handle_1_7", + "responses": { + "200": { + "description": "OK", + "content": { + "application/vnd.spring-boot.actuator.v3+json": { + "schema": { + "type": "object" + } + }, + "application/vnd.spring-boot.actuator.v2+json": { + "schema": { + "type": "object" + } + }, + "application/json": { + "schema": { + "type": "object" + } + } + } + } + } + } + }, + "/actuator/info": { + "get": { + "tags": [ + "Actuator" + ], + "operationId": "handle_1_8", + "responses": { + "200": { + "description": "OK", + "content": { + "application/vnd.spring-boot.actuator.v3+json": { + "schema": { + "type": "object" + } + }, + "application/vnd.spring-boot.actuator.v2+json": { + "schema": { + "type": "object" + } + }, + "application/json": { + "schema": { + "type": "object" + } + } + } + } + } + } + }, + "/actuator/heapdump": { + "get": { + "tags": [ + "Actuator" + ], + "operationId": "handle_1_9", + "responses": { + "200": { + "description": "OK", + "content": { + "application/octet-stream": { + "schema": { + "type": "object" + } + } + } + } + } + } + }, + "/actuator/health/{*path}": { + "get": { + "tags": [ + "Actuator" + ], + "operationId": "handle_1_10", + "parameters": [ + { + "name": "*path", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/vnd.spring-boot.actuator.v3+json": { + "schema": { + "type": "object" + } + }, + "application/vnd.spring-boot.actuator.v2+json": { + "schema": { + "type": "object" + } + }, + "application/json": { + "schema": { + "type": "object" + } + } + } + } + } + } + }, + "/actuator/health": { + "get": { + "tags": [ + "Actuator" + ], + "operationId": "handle_1_11", + "responses": { + "200": { + "description": "OK", + "content": { + "application/vnd.spring-boot.actuator.v3+json": { + "schema": { + "type": "object" + } + }, + "application/vnd.spring-boot.actuator.v2+json": { + "schema": { + "type": "object" + } + }, + "application/json": { + "schema": { + "type": "object" + } + } + } + } + } + } + }, + "/actuator/env/{toMatch}": { + "get": { + "tags": [ + "Actuator" + ], + "operationId": "handle_1_12", + "parameters": [ + { + "name": "toMatch", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/vnd.spring-boot.actuator.v3+json": { + "schema": { + "type": "object" + } + }, + "application/vnd.spring-boot.actuator.v2+json": { + "schema": { + "type": "object" + } + }, + "application/json": { + "schema": { + "type": "object" + } + } + } + } + } + } + }, + "/actuator/env": { + "get": { + "tags": [ + "Actuator" + ], + "operationId": "handle_1_13", + "responses": { + "200": { + "description": "OK", + "content": { + "application/vnd.spring-boot.actuator.v3+json": { + "schema": { + "type": "object" + } + }, + "application/vnd.spring-boot.actuator.v2+json": { + "schema": { + "type": "object" + } + }, + "application/json": { + "schema": { + "type": "object" + } + } + } + } + } + } + }, + "/actuator/configprops/{prefix}": { + "get": { + "tags": [ + "Actuator" + ], + "operationId": "handle_1_14", + "parameters": [ + { + "name": "prefix", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/vnd.spring-boot.actuator.v3+json": { + "schema": { + "type": "object" + } + }, + "application/vnd.spring-boot.actuator.v2+json": { + "schema": { + "type": "object" + } + }, + "application/json": { + "schema": { + "type": "object" + } + } + } + } + } + } + }, + "/actuator/configprops": { + "get": { + "tags": [ + "Actuator" + ], + "operationId": "handle_1_15", + "responses": { + "200": { + "description": "OK", + "content": { + "application/vnd.spring-boot.actuator.v3+json": { + "schema": { + "type": "object" + } + }, + "application/vnd.spring-boot.actuator.v2+json": { + "schema": { + "type": "object" + } + }, + "application/json": { + "schema": { + "type": "object" + } + } + } + } + } + } + }, + "/actuator/conditions": { + "get": { + "tags": [ + "Actuator" + ], + "operationId": "handle_1_16", + "responses": { + "200": { + "description": "OK", + "content": { + "application/vnd.spring-boot.actuator.v3+json": { + "schema": { + "type": "object" + } + }, + "application/vnd.spring-boot.actuator.v2+json": { + "schema": { + "type": "object" + } + }, + "application/json": { + "schema": { + "type": "object" + } + } + } + } + } + } + }, + "/actuator/caches/{cache}": { + "get": { + "tags": [ + "Actuator" + ], + "operationId": "handle_1_17", + "parameters": [ + { + "name": "cache", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/vnd.spring-boot.actuator.v3+json": { + "schema": { + "type": "object" + } + }, + "application/vnd.spring-boot.actuator.v2+json": { + "schema": { + "type": "object" + } + }, + "application/json": { + "schema": { + "type": "object" + } + } + } + } + } + }, + "delete": { + "tags": [ + "Actuator" + ], + "operationId": "handle_1_21", + "parameters": [ + { + "name": "cache", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/vnd.spring-boot.actuator.v3+json": { + "schema": { + "type": "object" + } + }, + "application/vnd.spring-boot.actuator.v2+json": { + "schema": { + "type": "object" + } + }, + "application/json": { + "schema": { + "type": "object" + } + } + } + } + } + } + }, + "/actuator/caches": { + "get": { + "tags": [ + "Actuator" + ], + "operationId": "handle_1_18", + "responses": { + "200": { + "description": "OK", + "content": { + "application/vnd.spring-boot.actuator.v3+json": { + "schema": { + "type": "object" + } + }, + "application/vnd.spring-boot.actuator.v2+json": { + "schema": { + "type": "object" + } + }, + "application/json": { + "schema": { + "type": "object" + } + } + } + } + } + }, + "delete": { + "tags": [ + "Actuator" + ], + "operationId": "handle_1_20", + "responses": { + "200": { + "description": "OK", + "content": { + "*/*": { + "schema": { + "type": "object" + } + } + } + } + } + } + }, + "/actuator/beans": { + "get": { + "tags": [ + "Actuator" + ], + "operationId": "handle_1_19", + "responses": { + "200": { + "description": "OK", + "content": { + "application/vnd.spring-boot.actuator.v3+json": { + "schema": { + "type": "object" + } + }, + "application/vnd.spring-boot.actuator.v2+json": { + "schema": { + "type": "object" + } + }, + "application/json": { + "schema": { + "type": "object" + } + } + } + } + } + } + }, + "/actuator": { + "get": { + "tags": [ + "Actuator" + ], + "summary": "Actuator root web endpoint", + "operationId": "links", + "responses": { + "200": { + "description": "OK", + "content": { + "application/vnd.spring-boot.actuator.v3+json": { + "schema": { + "type": "object", + "additionalProperties": { + "type": "object", + "additionalProperties": { + "$ref": "#/components/schemas/Link" + } + } + } + }, + "application/vnd.spring-boot.actuator.v2+json": { + "schema": { + "type": "object", + "additionalProperties": { + "type": "object", + "additionalProperties": { + "$ref": "#/components/schemas/Link" + } + } + } + }, + "application/json": { + "schema": { + "type": "object", + "additionalProperties": { + "type": "object", + "additionalProperties": { + "$ref": "#/components/schemas/Link" + } + } + } + } + } + } + } + } + } + }, + "components": { + "schemas": { + "Link": { + "type": "object", + "properties": { + "href": { + "type": "string" + }, + "templated": { + "type": "boolean" + } + } + } + } + } +} \ No newline at end of file diff --git a/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/v30/app186/SpringDocApp186Test.java b/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/v30/app186/SpringDocApp186Test.java new file mode 100644 index 000000000..40328a9fb --- /dev/null +++ b/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/v30/app186/SpringDocApp186Test.java @@ -0,0 +1,131 @@ +/* + * + * * + * * * + * * * * Copyright 2019-2022 the original author or authors. + * * * * + * * * * Licensed under the Apache License, Version 2.0 (the "License"); + * * * * you may not use this file except in compliance with the License. + * * * * You may obtain a copy of the License at + * * * * + * * * * https://www.apache.org/licenses/LICENSE-2.0 + * * * * + * * * * Unless required by applicable law or agreed to in writing, software + * * * * distributed under the License is distributed on an "AS IS" BASIS, + * * * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * * * * See the License for the specific language governing permissions and + * * * * limitations under the License. + * * * + * * + * + */ + +package test.org.springdoc.api.v30.app186; + +import static org.hamcrest.Matchers.is; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import java.nio.charset.StandardCharsets; + +import org.junit.jupiter.api.Test; +import org.skyscreamer.jsonassert.Customization; +import org.skyscreamer.jsonassert.JSONAssert; +import org.skyscreamer.jsonassert.JSONCompareMode; +import org.skyscreamer.jsonassert.ValueMatcher; +import org.skyscreamer.jsonassert.comparator.CustomComparator; +import org.skyscreamer.jsonassert.comparator.JSONComparator; +import org.springdoc.core.Constants; +import org.springdoc.core.GroupedOpenApi; +import org.springdoc.core.customizers.OpenApiCustomiser; +import org.springdoc.core.customizers.OperationCustomizer; +import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointProperties; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.Bean; +import org.springframework.test.context.TestPropertySource; +import org.springframework.test.web.servlet.MvcResult; + +import test.org.springdoc.api.v30.AbstractSpringDocV30Test; + +import static org.springdoc.core.Constants.ALL_PATTERN; + +@TestPropertySource(properties = { "springdoc.show-actuator=true", + "springdoc.group-configs[0].group=group-actuator-as-properties", + "springdoc.group-configs[0].paths-to-match=${management.endpoints.web.base-path:/actuator}/**", + "management.endpoints.enabled-by-default=true", + "management.endpoints.web.exposure.include=*", + "management.endpoints.web.exposure.exclude=functions, shutdown"}) +public class SpringDocApp186Test extends AbstractSpringDocV30Test { + + private static final JSONComparator STRICT_IGNORING_OPERATION_ID = new CustomComparator(JSONCompareMode.STRICT, + Customization.customization( + "paths.*.*.operationId" + , new ValueMatcher() { + @Override + public boolean equal(Object o1, Object o2) { + return true; + } + })); + + @SpringBootApplication + static class SpringDocTestApp { + + @Bean + public GroupedOpenApi asCodeCheckBackwardsCompatibility(OpenApiCustomiser actuatorOpenApiCustomiser, + OperationCustomizer actuatorCustomizer, WebEndpointProperties endpointProperties) { + return GroupedOpenApi.builder() + .group("group-actuator-as-code-check-backwards-compatibility") + .pathsToMatch(endpointProperties.getBasePath()+ ALL_PATTERN) + .addOpenApiCustomiser(actuatorOpenApiCustomiser) + .addOperationCustomizer(actuatorCustomizer) + .build(); + } + + @Bean + public GroupedOpenApi asCode(WebEndpointProperties endpointProperties) { + return GroupedOpenApi.builder() + .group("group-actuator-as-code") + .pathsToMatch(endpointProperties.getBasePath()+ ALL_PATTERN) + .build(); + } + } + + private void assertBodyApp186(MvcResult result) throws Exception { + String content = result.getResponse().getContentAsString(StandardCharsets.UTF_8); + JSONAssert.assertEquals(getContent("results/3.0.1/app186.json"), content, STRICT_IGNORING_OPERATION_ID); + } + + @Test + public void testApp() throws Exception { + mockMvc.perform(get(Constants.DEFAULT_API_DOCS_URL)) + .andExpect(jsonPath("$.openapi", is("3.0.1"))) + .andExpect(status().isOk()) + .andExpect(this::assertBodyApp186); + } + + @Test + public void testGroupActuatorAsCodeCheckBackwardsCompatibility() throws Exception { + mockMvc.perform(get(Constants.DEFAULT_API_DOCS_URL + "/group-actuator-as-code-check-backwards-compatibility")) + .andExpect(jsonPath("$.openapi", is("3.0.1"))) + .andExpect(status().isOk()) + .andExpect(this::assertBodyApp186); + } + + @Test + public void testGroupActuatorAsCode() throws Exception { + mockMvc.perform(get(Constants.DEFAULT_API_DOCS_URL + "/group-actuator-as-code")) + .andExpect(jsonPath("$.openapi", is("3.0.1"))) + .andExpect(status().isOk()) + .andExpect(this::assertBodyApp186); + } + + @Test + public void testGroupActuatorAsProperties() throws Exception { + mockMvc.perform(get(Constants.DEFAULT_API_DOCS_URL + "/group-actuator-as-properties")) + .andExpect(jsonPath("$.openapi", is("3.0.1"))) + .andExpect(status().isOk()) + .andExpect(this::assertBodyApp186); + } + +} \ No newline at end of file diff --git a/springdoc-openapi-webmvc-core/src/test/resources/results/3.0.1/app186.json b/springdoc-openapi-webmvc-core/src/test/resources/results/3.0.1/app186.json new file mode 100644 index 000000000..5488e8e32 --- /dev/null +++ b/springdoc-openapi-webmvc-core/src/test/resources/results/3.0.1/app186.json @@ -0,0 +1,828 @@ +{ + "openapi": "3.0.1", + "info": { + "title": "OpenAPI definition", + "version": "v0" + }, + "servers": [ + { + "url": "http://localhost", + "description": "Generated server url" + } + ], + "tags": [ + { + "name": "Actuator", + "description": "Monitor and interact", + "externalDocs": { + "description": "Spring Boot Actuator Web API Documentation", + "url": "https://docs.spring.io/spring-boot/docs/current/actuator-api/html/" + } + } + ], + "paths": { + "/actuator/loggers/{name}": { + "get": { + "tags": [ + "Actuator" + ], + "summary": "Actuator web endpoint 'loggers-name'", + "operationId": "loggers-name_2", + "parameters": [ + { + "name": "name", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/vnd.spring-boot.actuator.v3+json": { + "schema": { + "type": "object" + } + }, + "application/vnd.spring-boot.actuator.v2+json": { + "schema": { + "type": "object" + } + }, + "application/json": { + "schema": { + "type": "object" + } + } + } + } + } + }, + "post": { + "tags": [ + "Actuator" + ], + "summary": "Actuator web endpoint 'loggers-name'", + "operationId": "loggers-name", + "parameters": [ + { + "name": "name", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "string", + "enum": [ + "TRACE", + "DEBUG", + "INFO", + "WARN", + "ERROR", + "FATAL", + "OFF" + ] + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "*/*": { + "schema": { + "type": "object" + } + } + } + } + } + } + }, + "/actuator": { + "get": { + "tags": [ + "Actuator" + ], + "summary": "Actuator root web endpoint", + "operationId": "links", + "responses": { + "200": { + "description": "OK", + "content": { + "application/vnd.spring-boot.actuator.v3+json": { + "schema": { + "type": "object", + "additionalProperties": { + "type": "object", + "additionalProperties": { + "$ref": "#/components/schemas/Link" + } + } + } + }, + "application/vnd.spring-boot.actuator.v2+json": { + "schema": { + "type": "object", + "additionalProperties": { + "type": "object", + "additionalProperties": { + "$ref": "#/components/schemas/Link" + } + } + } + }, + "application/json": { + "schema": { + "type": "object", + "additionalProperties": { + "type": "object", + "additionalProperties": { + "$ref": "#/components/schemas/Link" + } + } + } + } + } + } + } + } + }, + "/actuator/threaddump": { + "get": { + "tags": [ + "Actuator" + ], + "summary": "Actuator web endpoint 'threaddump'", + "operationId": "threaddump_2", + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain;charset=UTF-8": { + "schema": { + "type": "object" + } + }, + "application/vnd.spring-boot.actuator.v3+json": { + "schema": { + "type": "object" + } + }, + "application/vnd.spring-boot.actuator.v2+json": { + "schema": { + "type": "object" + } + }, + "application/json": { + "schema": { + "type": "object" + } + } + } + } + } + } + }, + "/actuator/scheduledtasks": { + "get": { + "tags": [ + "Actuator" + ], + "summary": "Actuator web endpoint 'scheduledtasks'", + "operationId": "scheduledtasks", + "responses": { + "200": { + "description": "OK", + "content": { + "application/vnd.spring-boot.actuator.v3+json": { + "schema": { + "type": "object" + } + }, + "application/vnd.spring-boot.actuator.v2+json": { + "schema": { + "type": "object" + } + }, + "application/json": { + "schema": { + "type": "object" + } + } + } + } + } + } + }, + "/actuator/metrics": { + "get": { + "tags": [ + "Actuator" + ], + "summary": "Actuator web endpoint 'metrics'", + "operationId": "metrics", + "responses": { + "200": { + "description": "OK", + "content": { + "application/vnd.spring-boot.actuator.v3+json": { + "schema": { + "type": "object" + } + }, + "application/vnd.spring-boot.actuator.v2+json": { + "schema": { + "type": "object" + } + }, + "application/json": { + "schema": { + "type": "object" + } + } + } + } + } + } + }, + "/actuator/metrics/{requiredMetricName}": { + "get": { + "tags": [ + "Actuator" + ], + "summary": "Actuator web endpoint 'metrics-requiredMetricName'", + "operationId": "metrics-requiredMetricName", + "parameters": [ + { + "name": "requiredMetricName", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/vnd.spring-boot.actuator.v3+json": { + "schema": { + "type": "object" + } + }, + "application/vnd.spring-boot.actuator.v2+json": { + "schema": { + "type": "object" + } + }, + "application/json": { + "schema": { + "type": "object" + } + } + } + } + } + } + }, + "/actuator/mappings": { + "get": { + "tags": [ + "Actuator" + ], + "summary": "Actuator web endpoint 'mappings'", + "operationId": "mappings", + "responses": { + "200": { + "description": "OK", + "content": { + "application/vnd.spring-boot.actuator.v3+json": { + "schema": { + "type": "object" + } + }, + "application/vnd.spring-boot.actuator.v2+json": { + "schema": { + "type": "object" + } + }, + "application/json": { + "schema": { + "type": "object" + } + } + } + } + } + } + }, + "/actuator/loggers": { + "get": { + "tags": [ + "Actuator" + ], + "summary": "Actuator web endpoint 'loggers'", + "operationId": "loggers", + "responses": { + "200": { + "description": "OK", + "content": { + "application/vnd.spring-boot.actuator.v3+json": { + "schema": { + "type": "object" + } + }, + "application/vnd.spring-boot.actuator.v2+json": { + "schema": { + "type": "object" + } + }, + "application/json": { + "schema": { + "type": "object" + } + } + } + } + } + } + }, + "/actuator/info": { + "get": { + "tags": [ + "Actuator" + ], + "summary": "Actuator web endpoint 'info'", + "operationId": "info", + "responses": { + "200": { + "description": "OK", + "content": { + "application/vnd.spring-boot.actuator.v3+json": { + "schema": { + "type": "object" + } + }, + "application/vnd.spring-boot.actuator.v2+json": { + "schema": { + "type": "object" + } + }, + "application/json": { + "schema": { + "type": "object" + } + } + } + } + } + } + }, + "/actuator/heapdump": { + "get": { + "tags": [ + "Actuator" + ], + "summary": "Actuator web endpoint 'heapdump'", + "operationId": "heapdump", + "responses": { + "200": { + "description": "OK", + "content": { + "application/octet-stream": { + "schema": { + "type": "object" + } + } + } + } + } + } + }, + "/actuator/health": { + "get": { + "tags": [ + "Actuator" + ], + "summary": "Actuator web endpoint 'health'", + "operationId": "health", + "responses": { + "200": { + "description": "OK", + "content": { + "application/vnd.spring-boot.actuator.v3+json": { + "schema": { + "type": "object" + } + }, + "application/vnd.spring-boot.actuator.v2+json": { + "schema": { + "type": "object" + } + }, + "application/json": { + "schema": { + "type": "object" + } + } + } + } + } + } + }, + "/actuator/health/**": { + "get": { + "tags": [ + "Actuator" + ], + "summary": "Actuator web endpoint 'health-path'", + "operationId": "health-path", + "responses": { + "200": { + "description": "OK", + "content": { + "application/vnd.spring-boot.actuator.v3+json": { + "schema": { + "type": "object" + } + }, + "application/vnd.spring-boot.actuator.v2+json": { + "schema": { + "type": "object" + } + }, + "application/json": { + "schema": { + "type": "object" + } + } + } + } + } + } + }, + "/actuator/env": { + "get": { + "tags": [ + "Actuator" + ], + "summary": "Actuator web endpoint 'env'", + "operationId": "env", + "responses": { + "200": { + "description": "OK", + "content": { + "application/vnd.spring-boot.actuator.v3+json": { + "schema": { + "type": "object" + } + }, + "application/vnd.spring-boot.actuator.v2+json": { + "schema": { + "type": "object" + } + }, + "application/json": { + "schema": { + "type": "object" + } + } + } + } + } + } + }, + "/actuator/env/{toMatch}": { + "get": { + "tags": [ + "Actuator" + ], + "summary": "Actuator web endpoint 'env-toMatch'", + "operationId": "env-toMatch", + "parameters": [ + { + "name": "toMatch", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/vnd.spring-boot.actuator.v3+json": { + "schema": { + "type": "object" + } + }, + "application/vnd.spring-boot.actuator.v2+json": { + "schema": { + "type": "object" + } + }, + "application/json": { + "schema": { + "type": "object" + } + } + } + } + } + } + }, + "/actuator/configprops": { + "get": { + "tags": [ + "Actuator" + ], + "summary": "Actuator web endpoint 'configprops'", + "operationId": "configprops", + "responses": { + "200": { + "description": "OK", + "content": { + "application/vnd.spring-boot.actuator.v3+json": { + "schema": { + "type": "object" + } + }, + "application/vnd.spring-boot.actuator.v2+json": { + "schema": { + "type": "object" + } + }, + "application/json": { + "schema": { + "type": "object" + } + } + } + } + } + } + }, + "/actuator/configprops/{prefix}": { + "get": { + "tags": [ + "Actuator" + ], + "summary": "Actuator web endpoint 'configprops-prefix'", + "operationId": "configprops-prefix", + "parameters": [ + { + "name": "prefix", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/vnd.spring-boot.actuator.v3+json": { + "schema": { + "type": "object" + } + }, + "application/vnd.spring-boot.actuator.v2+json": { + "schema": { + "type": "object" + } + }, + "application/json": { + "schema": { + "type": "object" + } + } + } + } + } + } + }, + "/actuator/conditions": { + "get": { + "tags": [ + "Actuator" + ], + "summary": "Actuator web endpoint 'conditions'", + "operationId": "conditions", + "responses": { + "200": { + "description": "OK", + "content": { + "application/vnd.spring-boot.actuator.v3+json": { + "schema": { + "type": "object" + } + }, + "application/vnd.spring-boot.actuator.v2+json": { + "schema": { + "type": "object" + } + }, + "application/json": { + "schema": { + "type": "object" + } + } + } + } + } + } + }, + "/actuator/caches": { + "get": { + "tags": [ + "Actuator" + ], + "summary": "Actuator web endpoint 'caches'", + "operationId": "caches", + "responses": { + "200": { + "description": "OK", + "content": { + "application/vnd.spring-boot.actuator.v3+json": { + "schema": { + "type": "object" + } + }, + "application/vnd.spring-boot.actuator.v2+json": { + "schema": { + "type": "object" + } + }, + "application/json": { + "schema": { + "type": "object" + } + } + } + } + } + }, + "delete": { + "tags": [ + "Actuator" + ], + "summary": "Actuator web endpoint 'caches'", + "operationId": "caches_2", + "responses": { + "200": { + "description": "OK", + "content": { + "*/*": { + "schema": { + "type": "object" + } + } + } + } + } + } + }, + "/actuator/caches/{cache}": { + "get": { + "tags": [ + "Actuator" + ], + "summary": "Actuator web endpoint 'caches-cache'", + "operationId": "caches-cache", + "parameters": [ + { + "name": "cache", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/vnd.spring-boot.actuator.v3+json": { + "schema": { + "type": "object" + } + }, + "application/vnd.spring-boot.actuator.v2+json": { + "schema": { + "type": "object" + } + }, + "application/json": { + "schema": { + "type": "object" + } + } + } + } + } + }, + "delete": { + "tags": [ + "Actuator" + ], + "summary": "Actuator web endpoint 'caches-cache'", + "operationId": "caches-cache_2", + "parameters": [ + { + "name": "cache", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/vnd.spring-boot.actuator.v3+json": { + "schema": { + "type": "object" + } + }, + "application/vnd.spring-boot.actuator.v2+json": { + "schema": { + "type": "object" + } + }, + "application/json": { + "schema": { + "type": "object" + } + } + } + } + } + } + }, + "/actuator/beans": { + "get": { + "tags": [ + "Actuator" + ], + "summary": "Actuator web endpoint 'beans'", + "operationId": "beans", + "responses": { + "200": { + "description": "OK", + "content": { + "application/vnd.spring-boot.actuator.v3+json": { + "schema": { + "type": "object" + } + }, + "application/vnd.spring-boot.actuator.v2+json": { + "schema": { + "type": "object" + } + }, + "application/json": { + "schema": { + "type": "object" + } + } + } + } + } + } + } + }, + "components": { + "schemas": { + "Link": { + "type": "object", + "properties": { + "href": { + "type": "string" + }, + "templated": { + "type": "boolean" + } + } + } + } + } +} \ No newline at end of file From 1b64ec7eb0f1aa232153262a638c9af8b2d20dcb Mon Sep 17 00:00:00 2001 From: christophejan <69955393+christophejan@users.noreply.github.com> Date: Mon, 2 May 2022 08:19:59 +0200 Subject: [PATCH 2/2] Switch from statefull to stateless actuator customizers - Actuator operation ids should be unique across each OpenAPI definition not across all OpenAPI definitions - Statefull cutomizer beans seems odd --- .../ActuatorOpenApiCustomizer.java | 72 ++++++++++++++----- .../ActuatorOperationCustomizer.java | 15 ---- .../api/app186/SpringDocApp186Test.java | 33 ++------- .../src/test/resources/results/app146-1.json | 38 +++++----- .../src/test/resources/results/app147-1.json | 38 +++++----- .../src/test/resources/results/app148-2.json | 38 +++++----- .../src/test/resources/results/app186.json | 40 +++++------ .../api/v30/app186/SpringDocApp186Test.java | 33 ++------- .../resources/results/3.0.1/app146-1.json | 6 +- .../resources/results/3.0.1/app147-1.json | 8 +-- .../resources/results/3.0.1/app148-2.json | 6 +- .../test/resources/results/3.0.1/app186.json | 6 +- 12 files changed, 152 insertions(+), 181 deletions(-) diff --git a/springdoc-openapi-common/src/main/java/org/springdoc/core/customizers/ActuatorOpenApiCustomizer.java b/springdoc-openapi-common/src/main/java/org/springdoc/core/customizers/ActuatorOpenApiCustomizer.java index e3203cc37..385a99748 100644 --- a/springdoc-openapi-common/src/main/java/org/springdoc/core/customizers/ActuatorOpenApiCustomizer.java +++ b/springdoc-openapi-common/src/main/java/org/springdoc/core/customizers/ActuatorOpenApiCustomizer.java @@ -22,13 +22,19 @@ package org.springdoc.core.customizers; +import java.util.Comparator; +import java.util.HashSet; import java.util.List; +import java.util.Map.Entry; import java.util.Optional; +import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; +import java.util.stream.Stream; import io.swagger.v3.oas.models.OpenAPI; import io.swagger.v3.oas.models.PathItem; +import io.swagger.v3.oas.models.Paths; import io.swagger.v3.oas.models.media.StringSchema; import io.swagger.v3.oas.models.parameters.Parameter; import io.swagger.v3.oas.models.parameters.PathParameter; @@ -63,26 +69,54 @@ public ActuatorOpenApiCustomizer(WebEndpointProperties webEndpointProperties) { this.webEndpointProperties = webEndpointProperties; } + private Stream> actuatorPathEntryStream(OpenAPI openApi, String relativeSubPath) { + String pathPrefix = webEndpointProperties.getBasePath() + Optional.ofNullable(relativeSubPath).orElse(""); + return Optional.ofNullable(openApi.getPaths()) + .map(Paths::entrySet) + .map(Set::stream) + .map(s -> s.filter(entry -> entry.getKey().startsWith(pathPrefix))) + .orElse(Stream.empty()); + } + + private void handleActuatorPathParam(OpenAPI openApi) { + actuatorPathEntryStream(openApi, DEFAULT_PATH_SEPARATOR).forEach(stringPathItemEntry -> { + String path = stringPathItemEntry.getKey(); + Matcher matcher = pathPathern.matcher(path); + while (matcher.find()) { + String pathParam = matcher.group(1); + PathItem pathItem = stringPathItemEntry.getValue(); + pathItem.readOperations().forEach(operation -> { + List existingParameters = operation.getParameters(); + Optional existingParam = Optional.empty(); + if (!CollectionUtils.isEmpty(existingParameters)) + existingParam = existingParameters.stream().filter(p -> pathParam.equals(p.getName())).findAny(); + if (!existingParam.isPresent()) + operation.addParametersItem(new PathParameter().name(pathParam).schema(new StringSchema())); + }); + } + }); + } + + private void handleActuatorOperationIdUniqueness(OpenAPI openApi) { + Set usedOperationIds = new HashSet<>(); + actuatorPathEntryStream(openApi, null) + .sorted(Comparator.comparing(Entry::getKey)) + .forEachOrdered(stringPathItemEntry -> { + stringPathItemEntry.getValue().readOperations().forEach(operation -> { + String initialOperationId = operation.getOperationId(); + String uniqueOperationId = operation.getOperationId(); + int counter = 1; + while (!usedOperationIds.add(uniqueOperationId)) { + uniqueOperationId = initialOperationId + "_" + ++counter; + } + operation.setOperationId(uniqueOperationId); + }); + }); + } + @Override public void customise(OpenAPI openApi) { - if (!CollectionUtils.isEmpty(openApi.getPaths())) - openApi.getPaths().entrySet().stream() - .filter(stringPathItemEntry -> stringPathItemEntry.getKey().startsWith(webEndpointProperties.getBasePath() + DEFAULT_PATH_SEPARATOR)) - .forEach(stringPathItemEntry -> { - String path = stringPathItemEntry.getKey(); - Matcher matcher = pathPathern.matcher(path); - while (matcher.find()) { - String pathParam = matcher.group(1); - PathItem pathItem = stringPathItemEntry.getValue(); - pathItem.readOperations().forEach(operation -> { - List existingParameters = operation.getParameters(); - Optional existingParam = Optional.empty(); - if (!CollectionUtils.isEmpty(existingParameters)) - existingParam = existingParameters.stream().filter(p -> pathParam.equals(p.getName())).findAny(); - if (!existingParam.isPresent()) - operation.addParametersItem(new PathParameter().name(pathParam).schema(new StringSchema())); - }); - } - }); + handleActuatorPathParam(openApi); + handleActuatorOperationIdUniqueness(openApi); } } diff --git a/springdoc-openapi-common/src/main/java/org/springdoc/core/customizers/ActuatorOperationCustomizer.java b/springdoc-openapi-common/src/main/java/org/springdoc/core/customizers/ActuatorOperationCustomizer.java index 43a0ba9f0..4d3a3d8be 100644 --- a/springdoc-openapi-common/src/main/java/org/springdoc/core/customizers/ActuatorOperationCustomizer.java +++ b/springdoc-openapi-common/src/main/java/org/springdoc/core/customizers/ActuatorOperationCustomizer.java @@ -24,7 +24,6 @@ import java.lang.reflect.Field; import java.lang.reflect.Parameter; -import java.util.HashMap; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -45,7 +44,6 @@ import org.springframework.boot.actuate.endpoint.invoke.reflect.OperationMethod; import org.springframework.web.method.HandlerMethod; -import static org.apache.commons.lang3.math.NumberUtils.INTEGER_ONE; import static org.springdoc.core.providers.ActuatorProvider.getTag; /** @@ -54,11 +52,6 @@ */ public class ActuatorOperationCustomizer implements GlobalOperationCustomizer { - /** - * The Method count. - */ - private final HashMap methodCountMap = new HashMap<>(); - /** * The constant OPERATION. */ @@ -113,14 +106,6 @@ public Operation customize(Operation operation, HandlerMethod handlerMethod) { while (matcher.find()) { operationId = matcher.group(1); } - if (methodCountMap.containsKey(operationId)) { - Integer methodCount = methodCountMap.get(operationId) + 1; - methodCountMap.put(operationId, methodCount); - operationId = operationId + "_" + methodCount; - } - else - methodCountMap.put(operationId, INTEGER_ONE); - if (!summary.contains("$")) operation.setSummary(summary); operation.setOperationId(operationId); diff --git a/springdoc-openapi-webflux-core/src/test/java/test/org/springdoc/api/app186/SpringDocApp186Test.java b/springdoc-openapi-webflux-core/src/test/java/test/org/springdoc/api/app186/SpringDocApp186Test.java index 6f0047f0b..1b6018505 100644 --- a/springdoc-openapi-webflux-core/src/test/java/test/org/springdoc/api/app186/SpringDocApp186Test.java +++ b/springdoc-openapi-webflux-core/src/test/java/test/org/springdoc/api/app186/SpringDocApp186Test.java @@ -22,14 +22,7 @@ import static org.springdoc.core.Constants.ALL_PATTERN; -import org.json.JSONException; import org.junit.jupiter.api.Test; -import org.skyscreamer.jsonassert.Customization; -import org.skyscreamer.jsonassert.JSONAssert; -import org.skyscreamer.jsonassert.JSONCompareMode; -import org.skyscreamer.jsonassert.ValueMatcher; -import org.skyscreamer.jsonassert.comparator.CustomComparator; -import org.skyscreamer.jsonassert.comparator.JSONComparator; import org.springdoc.core.Constants; import org.springdoc.core.GroupedOpenApi; import org.springdoc.core.customizers.OpenApiCustomiser; @@ -52,16 +45,6 @@ "management.endpoints.web.exposure.exclude=functions, shutdown"}) public class SpringDocApp186Test extends AbstractCommonTest { - private static final JSONComparator STRICT_IGNORING_OPERATION_ID = new CustomComparator(JSONCompareMode.STRICT, - Customization.customization( - "paths.*.*.operationId" - , new ValueMatcher() { - @Override - public boolean equal(Object o1, Object o2) { - return true; - } - })); - @SpringBootApplication @ComponentScan(basePackages = { "org.springdoc", "test.org.springdoc.api.app186" }) static class SpringDocTestApp { @@ -86,40 +69,32 @@ public GroupedOpenApi asCode(WebEndpointProperties endpointProperties) { } } - private void assertBodyApp186(String content) { - try { - JSONAssert.assertEquals(getContent("results/app186.json"), content, STRICT_IGNORING_OPERATION_ID); - } catch (JSONException e) { - throw new RuntimeException(e); - } - } - @Test public void testApp() throws Exception { webTestClient.get().uri(Constants.DEFAULT_API_DOCS_URL).exchange() .expectStatus().isOk() - .expectBody(String.class).value(this::assertBodyApp186); + .expectBody().json(getContent("results/app186.json"), true); } @Test public void testGroupActuatorAsCodeCheckBackwardsCompatibility() throws Exception { webTestClient.get().uri(Constants.DEFAULT_API_DOCS_URL + "/group-actuator-as-code-check-backwards-compatibility").exchange() .expectStatus().isOk() - .expectBody(String.class).value(this::assertBodyApp186); + .expectBody().json(getContent("results/app186.json"), true); } @Test public void testGroupActuatorAsCode() throws Exception { webTestClient.get().uri(Constants.DEFAULT_API_DOCS_URL + "/group-actuator-as-code").exchange() .expectStatus().isOk() - .expectBody(String.class).value(this::assertBodyApp186); + .expectBody().json(getContent("results/app186.json"), true); } @Test public void testGroupActuatorAsProperties() throws Exception { webTestClient.get().uri(Constants.DEFAULT_API_DOCS_URL + "/group-actuator-as-properties").exchange() .expectStatus().isOk() - .expectBody(String.class).value(this::assertBodyApp186); + .expectBody().json(getContent("results/app186.json"), true); } } \ No newline at end of file diff --git a/springdoc-openapi-webflux-core/src/test/resources/results/app146-1.json b/springdoc-openapi-webflux-core/src/test/resources/results/app146-1.json index e51be718a..92c2f3cb2 100644 --- a/springdoc-openapi-webflux-core/src/test/resources/results/app146-1.json +++ b/springdoc-openapi-webflux-core/src/test/resources/results/app146-1.json @@ -26,7 +26,7 @@ "tags": [ "Actuator" ], - "operationId": "handle_1_6", + "operationId": "handle_5", "parameters": [ { "name": "name", @@ -147,7 +147,7 @@ "tags": [ "Actuator" ], - "operationId": "handle_1_2", + "operationId": "handle_1", "responses": { "200": { "description": "OK", @@ -177,7 +177,7 @@ "tags": [ "Actuator" ], - "operationId": "handle_1_3", + "operationId": "handle_2", "parameters": [ { "name": "requiredMetricName", @@ -217,7 +217,7 @@ "tags": [ "Actuator" ], - "operationId": "handle_1_4", + "operationId": "handle_3", "responses": { "200": { "description": "OK", @@ -247,7 +247,7 @@ "tags": [ "Actuator" ], - "operationId": "handle_1_5", + "operationId": "handle_4", "responses": { "200": { "description": "OK", @@ -277,7 +277,7 @@ "tags": [ "Actuator" ], - "operationId": "handle_1_7", + "operationId": "handle_6", "responses": { "200": { "description": "OK", @@ -307,7 +307,7 @@ "tags": [ "Actuator" ], - "operationId": "handle_1_8", + "operationId": "handle_7", "responses": { "200": { "description": "OK", @@ -337,7 +337,7 @@ "tags": [ "Actuator" ], - "operationId": "handle_1_9", + "operationId": "handle_8", "responses": { "200": { "description": "OK", @@ -357,7 +357,7 @@ "tags": [ "Actuator" ], - "operationId": "handle_1_10", + "operationId": "handle_9", "responses": { "200": { "description": "OK", @@ -387,7 +387,7 @@ "tags": [ "Actuator" ], - "operationId": "handle_1_11", + "operationId": "handle_10", "parameters": [ { "name": "toMatch", @@ -427,7 +427,7 @@ "tags": [ "Actuator" ], - "operationId": "handle_1_12", + "operationId": "handle_11", "responses": { "200": { "description": "OK", @@ -457,7 +457,7 @@ "tags": [ "Actuator" ], - "operationId": "handle_1_13", + "operationId": "handle_12", "parameters": [ { "name": "prefix", @@ -497,7 +497,7 @@ "tags": [ "Actuator" ], - "operationId": "handle_1_14", + "operationId": "handle_13", "responses": { "200": { "description": "OK", @@ -527,7 +527,7 @@ "tags": [ "Actuator" ], - "operationId": "handle_1_15", + "operationId": "handle_14", "responses": { "200": { "description": "OK", @@ -557,7 +557,7 @@ "tags": [ "Actuator" ], - "operationId": "handle_1_16", + "operationId": "handle_15", "parameters": [ { "name": "cache", @@ -595,7 +595,7 @@ "tags": [ "Actuator" ], - "operationId": "handle_1_20", + "operationId": "handle_19", "parameters": [ { "name": "cache", @@ -635,7 +635,7 @@ "tags": [ "Actuator" ], - "operationId": "handle_1_17", + "operationId": "handle_16", "responses": { "200": { "description": "OK", @@ -663,7 +663,7 @@ "tags": [ "Actuator" ], - "operationId": "handle_1_19", + "operationId": "handle_18", "responses": { "200": { "description": "OK", @@ -683,7 +683,7 @@ "tags": [ "Actuator" ], - "operationId": "handle_1_18", + "operationId": "handle_17", "responses": { "200": { "description": "OK", diff --git a/springdoc-openapi-webflux-core/src/test/resources/results/app147-1.json b/springdoc-openapi-webflux-core/src/test/resources/results/app147-1.json index 7bad9dfe5..b049d12c0 100644 --- a/springdoc-openapi-webflux-core/src/test/resources/results/app147-1.json +++ b/springdoc-openapi-webflux-core/src/test/resources/results/app147-1.json @@ -26,7 +26,7 @@ "tags": [ "Actuator" ], - "operationId": "handle_1_6", + "operationId": "handle_5", "parameters": [ { "name": "name", @@ -147,7 +147,7 @@ "tags": [ "Actuator" ], - "operationId": "handle_1_2", + "operationId": "handle_1", "responses": { "200": { "description": "OK", @@ -177,7 +177,7 @@ "tags": [ "Actuator" ], - "operationId": "handle_1_3", + "operationId": "handle_2", "parameters": [ { "name": "requiredMetricName", @@ -217,7 +217,7 @@ "tags": [ "Actuator" ], - "operationId": "handle_1_4", + "operationId": "handle_3", "responses": { "200": { "description": "OK", @@ -247,7 +247,7 @@ "tags": [ "Actuator" ], - "operationId": "handle_1_5", + "operationId": "handle_4", "responses": { "200": { "description": "OK", @@ -277,7 +277,7 @@ "tags": [ "Actuator" ], - "operationId": "handle_1_7", + "operationId": "handle_6", "responses": { "200": { "description": "OK", @@ -307,7 +307,7 @@ "tags": [ "Actuator" ], - "operationId": "handle_1_8", + "operationId": "handle_7", "responses": { "200": { "description": "OK", @@ -337,7 +337,7 @@ "tags": [ "Actuator" ], - "operationId": "handle_1_9", + "operationId": "handle_8", "responses": { "200": { "description": "OK", @@ -357,7 +357,7 @@ "tags": [ "Actuator" ], - "operationId": "handle_1_10", + "operationId": "handle_9", "responses": { "200": { "description": "OK", @@ -387,7 +387,7 @@ "tags": [ "Actuator" ], - "operationId": "handle_1_11", + "operationId": "handle_10", "parameters": [ { "name": "toMatch", @@ -427,7 +427,7 @@ "tags": [ "Actuator" ], - "operationId": "handle_1_12", + "operationId": "handle_11", "responses": { "200": { "description": "OK", @@ -457,7 +457,7 @@ "tags": [ "Actuator" ], - "operationId": "handle_1_13", + "operationId": "handle_12", "parameters": [ { "name": "prefix", @@ -497,7 +497,7 @@ "tags": [ "Actuator" ], - "operationId": "handle_1_14", + "operationId": "handle_13", "responses": { "200": { "description": "OK", @@ -527,7 +527,7 @@ "tags": [ "Actuator" ], - "operationId": "handle_1_15", + "operationId": "handle_14", "responses": { "200": { "description": "OK", @@ -557,7 +557,7 @@ "tags": [ "Actuator" ], - "operationId": "handle_1_16", + "operationId": "handle_15", "parameters": [ { "name": "cache", @@ -595,7 +595,7 @@ "tags": [ "Actuator" ], - "operationId": "handle_1_20", + "operationId": "handle_19", "parameters": [ { "name": "cache", @@ -635,7 +635,7 @@ "tags": [ "Actuator" ], - "operationId": "handle_1_17", + "operationId": "handle_16", "responses": { "200": { "description": "OK", @@ -663,7 +663,7 @@ "tags": [ "Actuator" ], - "operationId": "handle_1_19", + "operationId": "handle_18", "responses": { "200": { "description": "OK", @@ -683,7 +683,7 @@ "tags": [ "Actuator" ], - "operationId": "handle_1_18", + "operationId": "handle_17", "responses": { "200": { "description": "OK", diff --git a/springdoc-openapi-webflux-core/src/test/resources/results/app148-2.json b/springdoc-openapi-webflux-core/src/test/resources/results/app148-2.json index 51fb1fecf..82983a583 100644 --- a/springdoc-openapi-webflux-core/src/test/resources/results/app148-2.json +++ b/springdoc-openapi-webflux-core/src/test/resources/results/app148-2.json @@ -26,7 +26,7 @@ "tags": [ "Actuator" ], - "operationId": "handle_1_6", + "operationId": "handle_5", "parameters": [ { "name": "name", @@ -147,7 +147,7 @@ "tags": [ "Actuator" ], - "operationId": "handle_1_2", + "operationId": "handle_1", "responses": { "200": { "description": "OK", @@ -177,7 +177,7 @@ "tags": [ "Actuator" ], - "operationId": "handle_1_3", + "operationId": "handle_2", "parameters": [ { "name": "requiredMetricName", @@ -217,7 +217,7 @@ "tags": [ "Actuator" ], - "operationId": "handle_1_4", + "operationId": "handle_3", "responses": { "200": { "description": "OK", @@ -247,7 +247,7 @@ "tags": [ "Actuator" ], - "operationId": "handle_1_5", + "operationId": "handle_4", "responses": { "200": { "description": "OK", @@ -277,7 +277,7 @@ "tags": [ "Actuator" ], - "operationId": "handle_1_7", + "operationId": "handle_6", "responses": { "200": { "description": "OK", @@ -307,7 +307,7 @@ "tags": [ "Actuator" ], - "operationId": "handle_1_8", + "operationId": "handle_7", "responses": { "200": { "description": "OK", @@ -337,7 +337,7 @@ "tags": [ "Actuator" ], - "operationId": "handle_1_9", + "operationId": "handle_8", "responses": { "200": { "description": "OK", @@ -357,7 +357,7 @@ "tags": [ "Actuator" ], - "operationId": "handle_1_10", + "operationId": "handle_9", "responses": { "200": { "description": "OK", @@ -387,7 +387,7 @@ "tags": [ "Actuator" ], - "operationId": "handle_1_11", + "operationId": "handle_10", "parameters": [ { "name": "toMatch", @@ -427,7 +427,7 @@ "tags": [ "Actuator" ], - "operationId": "handle_1_12", + "operationId": "handle_11", "responses": { "200": { "description": "OK", @@ -457,7 +457,7 @@ "tags": [ "Actuator" ], - "operationId": "handle_1_13", + "operationId": "handle_12", "parameters": [ { "name": "prefix", @@ -497,7 +497,7 @@ "tags": [ "Actuator" ], - "operationId": "handle_1_14", + "operationId": "handle_13", "responses": { "200": { "description": "OK", @@ -527,7 +527,7 @@ "tags": [ "Actuator" ], - "operationId": "handle_1_15", + "operationId": "handle_14", "responses": { "200": { "description": "OK", @@ -557,7 +557,7 @@ "tags": [ "Actuator" ], - "operationId": "handle_1_16", + "operationId": "handle_15", "parameters": [ { "name": "cache", @@ -595,7 +595,7 @@ "tags": [ "Actuator" ], - "operationId": "handle_1_20", + "operationId": "handle_19", "parameters": [ { "name": "cache", @@ -635,7 +635,7 @@ "tags": [ "Actuator" ], - "operationId": "handle_1_17", + "operationId": "handle_16", "responses": { "200": { "description": "OK", @@ -663,7 +663,7 @@ "tags": [ "Actuator" ], - "operationId": "handle_1_19", + "operationId": "handle_18", "responses": { "200": { "description": "OK", @@ -683,7 +683,7 @@ "tags": [ "Actuator" ], - "operationId": "handle_1_18", + "operationId": "handle_17", "responses": { "200": { "description": "OK", diff --git a/springdoc-openapi-webflux-core/src/test/resources/results/app186.json b/springdoc-openapi-webflux-core/src/test/resources/results/app186.json index 02108d263..2b4d74f14 100644 --- a/springdoc-openapi-webflux-core/src/test/resources/results/app186.json +++ b/springdoc-openapi-webflux-core/src/test/resources/results/app186.json @@ -26,7 +26,7 @@ "tags": [ "Actuator" ], - "operationId": "handle_1_6", + "operationId": "handle_5", "parameters": [ { "name": "name", @@ -147,7 +147,7 @@ "tags": [ "Actuator" ], - "operationId": "handle_1_2", + "operationId": "handle_1", "responses": { "200": { "description": "OK", @@ -177,7 +177,7 @@ "tags": [ "Actuator" ], - "operationId": "handle_1_3", + "operationId": "handle_2", "parameters": [ { "name": "requiredMetricName", @@ -217,7 +217,7 @@ "tags": [ "Actuator" ], - "operationId": "handle_1_4", + "operationId": "handle_3", "responses": { "200": { "description": "OK", @@ -247,7 +247,7 @@ "tags": [ "Actuator" ], - "operationId": "handle_1_5", + "operationId": "handle_4", "responses": { "200": { "description": "OK", @@ -277,7 +277,7 @@ "tags": [ "Actuator" ], - "operationId": "handle_1_7", + "operationId": "handle_6", "responses": { "200": { "description": "OK", @@ -307,7 +307,7 @@ "tags": [ "Actuator" ], - "operationId": "handle_1_8", + "operationId": "handle_7", "responses": { "200": { "description": "OK", @@ -337,7 +337,7 @@ "tags": [ "Actuator" ], - "operationId": "handle_1_9", + "operationId": "handle_8", "responses": { "200": { "description": "OK", @@ -357,7 +357,7 @@ "tags": [ "Actuator" ], - "operationId": "handle_1_10", + "operationId": "handle_9", "parameters": [ { "name": "*path", @@ -397,7 +397,7 @@ "tags": [ "Actuator" ], - "operationId": "handle_1_11", + "operationId": "handle_10", "responses": { "200": { "description": "OK", @@ -427,7 +427,7 @@ "tags": [ "Actuator" ], - "operationId": "handle_1_12", + "operationId": "handle_11", "parameters": [ { "name": "toMatch", @@ -467,7 +467,7 @@ "tags": [ "Actuator" ], - "operationId": "handle_1_13", + "operationId": "handle_12", "responses": { "200": { "description": "OK", @@ -497,7 +497,7 @@ "tags": [ "Actuator" ], - "operationId": "handle_1_14", + "operationId": "handle_13", "parameters": [ { "name": "prefix", @@ -537,7 +537,7 @@ "tags": [ "Actuator" ], - "operationId": "handle_1_15", + "operationId": "handle_14", "responses": { "200": { "description": "OK", @@ -567,7 +567,7 @@ "tags": [ "Actuator" ], - "operationId": "handle_1_16", + "operationId": "handle_15", "responses": { "200": { "description": "OK", @@ -597,7 +597,7 @@ "tags": [ "Actuator" ], - "operationId": "handle_1_17", + "operationId": "handle_16", "parameters": [ { "name": "cache", @@ -635,7 +635,7 @@ "tags": [ "Actuator" ], - "operationId": "handle_1_21", + "operationId": "handle_20", "parameters": [ { "name": "cache", @@ -675,7 +675,7 @@ "tags": [ "Actuator" ], - "operationId": "handle_1_18", + "operationId": "handle_17", "responses": { "200": { "description": "OK", @@ -703,7 +703,7 @@ "tags": [ "Actuator" ], - "operationId": "handle_1_20", + "operationId": "handle_19", "responses": { "200": { "description": "OK", @@ -723,7 +723,7 @@ "tags": [ "Actuator" ], - "operationId": "handle_1_19", + "operationId": "handle_18", "responses": { "200": { "description": "OK", diff --git a/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/v30/app186/SpringDocApp186Test.java b/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/v30/app186/SpringDocApp186Test.java index 40328a9fb..83c1c3e9f 100644 --- a/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/v30/app186/SpringDocApp186Test.java +++ b/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/v30/app186/SpringDocApp186Test.java @@ -24,18 +24,11 @@ import static org.hamcrest.Matchers.is; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; -import java.nio.charset.StandardCharsets; - import org.junit.jupiter.api.Test; -import org.skyscreamer.jsonassert.Customization; -import org.skyscreamer.jsonassert.JSONAssert; -import org.skyscreamer.jsonassert.JSONCompareMode; -import org.skyscreamer.jsonassert.ValueMatcher; -import org.skyscreamer.jsonassert.comparator.CustomComparator; -import org.skyscreamer.jsonassert.comparator.JSONComparator; import org.springdoc.core.Constants; import org.springdoc.core.GroupedOpenApi; import org.springdoc.core.customizers.OpenApiCustomiser; @@ -44,7 +37,6 @@ import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.annotation.Bean; import org.springframework.test.context.TestPropertySource; -import org.springframework.test.web.servlet.MvcResult; import test.org.springdoc.api.v30.AbstractSpringDocV30Test; @@ -58,16 +50,6 @@ "management.endpoints.web.exposure.exclude=functions, shutdown"}) public class SpringDocApp186Test extends AbstractSpringDocV30Test { - private static final JSONComparator STRICT_IGNORING_OPERATION_ID = new CustomComparator(JSONCompareMode.STRICT, - Customization.customization( - "paths.*.*.operationId" - , new ValueMatcher() { - @Override - public boolean equal(Object o1, Object o2) { - return true; - } - })); - @SpringBootApplication static class SpringDocTestApp { @@ -91,17 +73,12 @@ public GroupedOpenApi asCode(WebEndpointProperties endpointProperties) { } } - private void assertBodyApp186(MvcResult result) throws Exception { - String content = result.getResponse().getContentAsString(StandardCharsets.UTF_8); - JSONAssert.assertEquals(getContent("results/3.0.1/app186.json"), content, STRICT_IGNORING_OPERATION_ID); - } - @Test public void testApp() throws Exception { mockMvc.perform(get(Constants.DEFAULT_API_DOCS_URL)) .andExpect(jsonPath("$.openapi", is("3.0.1"))) .andExpect(status().isOk()) - .andExpect(this::assertBodyApp186); + .andExpect(content().json(getContent("results/3.0.1/app186.json"), true)); } @Test @@ -109,7 +86,7 @@ public void testGroupActuatorAsCodeCheckBackwardsCompatibility() throws Exceptio mockMvc.perform(get(Constants.DEFAULT_API_DOCS_URL + "/group-actuator-as-code-check-backwards-compatibility")) .andExpect(jsonPath("$.openapi", is("3.0.1"))) .andExpect(status().isOk()) - .andExpect(this::assertBodyApp186); + .andExpect(content().json(getContent("results/3.0.1/app186.json"), true)); } @Test @@ -117,7 +94,7 @@ public void testGroupActuatorAsCode() throws Exception { mockMvc.perform(get(Constants.DEFAULT_API_DOCS_URL + "/group-actuator-as-code")) .andExpect(jsonPath("$.openapi", is("3.0.1"))) .andExpect(status().isOk()) - .andExpect(this::assertBodyApp186); + .andExpect(content().json(getContent("results/3.0.1/app186.json"), true)); } @Test @@ -125,7 +102,7 @@ public void testGroupActuatorAsProperties() throws Exception { mockMvc.perform(get(Constants.DEFAULT_API_DOCS_URL + "/group-actuator-as-properties")) .andExpect(jsonPath("$.openapi", is("3.0.1"))) .andExpect(status().isOk()) - .andExpect(this::assertBodyApp186); + .andExpect(content().json(getContent("results/3.0.1/app186.json"), true)); } } \ No newline at end of file diff --git a/springdoc-openapi-webmvc-core/src/test/resources/results/3.0.1/app146-1.json b/springdoc-openapi-webmvc-core/src/test/resources/results/3.0.1/app146-1.json index fe0684662..ba4820f8c 100644 --- a/springdoc-openapi-webmvc-core/src/test/resources/results/3.0.1/app146-1.json +++ b/springdoc-openapi-webmvc-core/src/test/resources/results/3.0.1/app146-1.json @@ -27,7 +27,7 @@ "Actuator" ], "summary": "Actuator web endpoint 'loggers-name'", - "operationId": "loggers-name_2", + "operationId": "loggers-name", "parameters": [ { "name": "name", @@ -66,7 +66,7 @@ "Actuator" ], "summary": "Actuator web endpoint 'loggers-name'", - "operationId": "loggers-name", + "operationId": "loggers-name_2", "parameters": [ { "name": "name", @@ -164,7 +164,7 @@ "Actuator" ], "summary": "Actuator web endpoint 'threaddump'", - "operationId": "threaddump_2", + "operationId": "threaddump", "responses": { "200": { "description": "OK", diff --git a/springdoc-openapi-webmvc-core/src/test/resources/results/3.0.1/app147-1.json b/springdoc-openapi-webmvc-core/src/test/resources/results/3.0.1/app147-1.json index fd8188a40..e9bf873dc 100644 --- a/springdoc-openapi-webmvc-core/src/test/resources/results/3.0.1/app147-1.json +++ b/springdoc-openapi-webmvc-core/src/test/resources/results/3.0.1/app147-1.json @@ -1,4 +1,4 @@ -{ + { "openapi": "3.0.1", "info": { "title": "OpenAPI definition", @@ -27,7 +27,7 @@ "Actuator" ], "summary": "Actuator web endpoint 'loggers-name'", - "operationId": "loggers-name_2", + "operationId": "loggers-name", "parameters": [ { "name": "name", @@ -66,7 +66,7 @@ "Actuator" ], "summary": "Actuator web endpoint 'loggers-name'", - "operationId": "loggers-name", + "operationId": "loggers-name_2", "parameters": [ { "name": "name", @@ -164,7 +164,7 @@ "Actuator" ], "summary": "Actuator web endpoint 'threaddump'", - "operationId": "threaddump_2", + "operationId": "threaddump", "responses": { "200": { "description": "OK", diff --git a/springdoc-openapi-webmvc-core/src/test/resources/results/3.0.1/app148-2.json b/springdoc-openapi-webmvc-core/src/test/resources/results/3.0.1/app148-2.json index 59eb5850a..2d632ab24 100644 --- a/springdoc-openapi-webmvc-core/src/test/resources/results/3.0.1/app148-2.json +++ b/springdoc-openapi-webmvc-core/src/test/resources/results/3.0.1/app148-2.json @@ -27,7 +27,7 @@ "Actuator" ], "summary": "Actuator web endpoint 'loggers-name'", - "operationId": "loggers-name_2", + "operationId": "loggers-name", "parameters": [ { "name": "name", @@ -66,7 +66,7 @@ "Actuator" ], "summary": "Actuator web endpoint 'loggers-name'", - "operationId": "loggers-name", + "operationId": "loggers-name_2", "parameters": [ { "name": "name", @@ -164,7 +164,7 @@ "Actuator" ], "summary": "Actuator web endpoint 'threaddump'", - "operationId": "threaddump_2", + "operationId": "threaddump", "responses": { "200": { "description": "OK", diff --git a/springdoc-openapi-webmvc-core/src/test/resources/results/3.0.1/app186.json b/springdoc-openapi-webmvc-core/src/test/resources/results/3.0.1/app186.json index 5488e8e32..c136df41f 100644 --- a/springdoc-openapi-webmvc-core/src/test/resources/results/3.0.1/app186.json +++ b/springdoc-openapi-webmvc-core/src/test/resources/results/3.0.1/app186.json @@ -27,7 +27,7 @@ "Actuator" ], "summary": "Actuator web endpoint 'loggers-name'", - "operationId": "loggers-name_2", + "operationId": "loggers-name", "parameters": [ { "name": "name", @@ -66,7 +66,7 @@ "Actuator" ], "summary": "Actuator web endpoint 'loggers-name'", - "operationId": "loggers-name", + "operationId": "loggers-name_2", "parameters": [ { "name": "name", @@ -164,7 +164,7 @@ "Actuator" ], "summary": "Actuator web endpoint 'threaddump'", - "operationId": "threaddump_2", + "operationId": "threaddump", "responses": { "200": { "description": "OK",