From 750a452fe8e5c11c23cc36a39e19e559de8d97ee Mon Sep 17 00:00:00 2001 From: Haotian Zhang <928016560@qq.com> Date: Thu, 30 Apr 2026 20:05:12 +0800 Subject: [PATCH] fix: split contract base-package for springdoc scan When @SpringBootApplication(scanBasePackages = {"a","b"}) declares multiple packages, PackageUtil.scanPackage returns them joined by a comma. The previous code passed the whole comma-joined string as a single package name to GroupedOpenApi.Builder#packagesToScan, so springdoc matched no controller and both the Polaris and TSF consoles showed an empty API list (the single-value case happened to be a valid package name and thus worked). Split the string by SPLITTER before handing it to packagesToScan so each declared package reaches springdoc as an independent entry. Add unit tests covering multi-value scanBasePackages, single-value regression, and the comma-separated spring.cloud.polaris.contract.base-package property. Co-Authored-By: Claude Opus 4.7 (1M context) Signed-off-by: Haotian Zhang <928016560@qq.com> --- .../PolarisSwaggerAutoConfiguration.java | 6 +- .../PolarisSwaggerAutoConfigurationTest.java | 123 ++++++++++++++++++ .../demo/provider/ProviderApplication.java | 2 +- .../src/main/resources/bootstrap.yml | 2 + 4 files changed, 131 insertions(+), 2 deletions(-) create mode 100644 spring-cloud-starter-tencent-polaris-contract/src/test/java/com/tencent/cloud/polaris/contract/config/PolarisSwaggerAutoConfigurationTest.java diff --git a/spring-cloud-starter-tencent-polaris-contract/src/main/java/com/tencent/cloud/polaris/contract/config/PolarisSwaggerAutoConfiguration.java b/spring-cloud-starter-tencent-polaris-contract/src/main/java/com/tencent/cloud/polaris/contract/config/PolarisSwaggerAutoConfiguration.java index eed0946d2..af6508532 100644 --- a/spring-cloud-starter-tencent-polaris-contract/src/main/java/com/tencent/cloud/polaris/contract/config/PolarisSwaggerAutoConfiguration.java +++ b/spring-cloud-starter-tencent-polaris-contract/src/main/java/com/tencent/cloud/polaris/contract/config/PolarisSwaggerAutoConfiguration.java @@ -67,6 +67,10 @@ public class PolarisSwaggerAutoConfiguration { @Bean public GroupedOpenApi polarisGroupedOpenApi(PolarisContractProperties polarisContractProperties) { String basePackage = PackageUtil.scanPackage(polarisContractProperties.getBasePackage()); + String[] basePackages = {}; + if (StringUtils.hasText(basePackage)) { + basePackages = basePackage.split(SPLITTER); + } String[] basePaths = {}; if (StringUtils.hasText(polarisContractProperties.getBasePath())) { basePaths = polarisContractProperties.getBasePath().split(SPLITTER); @@ -76,7 +80,7 @@ public class PolarisSwaggerAutoConfiguration { excludePaths = polarisContractProperties.getExcludePath().split(SPLITTER); } return GroupedOpenApi.builder() - .packagesToScan(basePackage) + .packagesToScan(basePackages) .pathsToMatch(basePaths) .pathsToExclude(excludePaths) .group(polarisContractProperties.getGroup()) diff --git a/spring-cloud-starter-tencent-polaris-contract/src/test/java/com/tencent/cloud/polaris/contract/config/PolarisSwaggerAutoConfigurationTest.java b/spring-cloud-starter-tencent-polaris-contract/src/test/java/com/tencent/cloud/polaris/contract/config/PolarisSwaggerAutoConfigurationTest.java new file mode 100644 index 000000000..7f66a9d0e --- /dev/null +++ b/spring-cloud-starter-tencent-polaris-contract/src/test/java/com/tencent/cloud/polaris/contract/config/PolarisSwaggerAutoConfigurationTest.java @@ -0,0 +1,123 @@ +/* + * Tencent is pleased to support the open source community by making spring-cloud-tencent available. + * + * Copyright (C) 2021 Tencent. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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.tencent.cloud.polaris.contract.config; + +import java.util.List; + +import com.tencent.cloud.polaris.contract.SwaggerContext; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springdoc.core.models.GroupedOpenApi; + +import org.springframework.boot.autoconfigure.SpringBootApplication; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Test for {@link PolarisSwaggerAutoConfiguration}. + * + * @author Haotian Zhang + */ +@DisplayName("PolarisSwaggerAutoConfiguration") +class PolarisSwaggerAutoConfigurationTest { + + private static final String MAIN_CLASS_KEY = "$MainClass"; + private static final String PACKAGE_A = "com.tencent.cloud.tsf.demo.provider"; + private static final String PACKAGE_B = "com.tencent.cloud.tsf"; + + private PolarisSwaggerAutoConfiguration autoConfiguration; + private PolarisContractProperties properties; + + @BeforeEach + void setUp() { + autoConfiguration = new PolarisSwaggerAutoConfiguration(); + properties = new PolarisContractProperties(); + properties.setGroup("polaris"); + // No externally-configured base-package; drive scan purely from @SpringBootApplication. + properties.setBasePackage(null); + } + + /** + * Reproduce the reported defect: when the application declares + * {@code @SpringBootApplication(scanBasePackages = {pkgA, pkgB})}, the generated + * {@link GroupedOpenApi} must expose the two packages as independent entries so + * springdoc can scan controllers in BOTH packages. Before the fix, the comma-joined + * string was passed as a single package name, causing springdoc to match nothing + * and the Polaris / TSF console API list to be empty. + */ + @DisplayName("multi-value scanBasePackages produces independent packagesToScan entries") + @Test + void testMultiValueScanBasePackagesSplitIntoIndependentEntries() { + // Arrange + SwaggerContext.setAttribute(MAIN_CLASS_KEY, MultiPackageApplication.class); + + // Act + GroupedOpenApi groupedOpenApi = autoConfiguration.polarisGroupedOpenApi(properties); + + // Assert + List packagesToScan = groupedOpenApi.getPackagesToScan(); + assertThat(packagesToScan) + .as("each scanBasePackages value must land as its own entry, not a comma-joined string") + .containsExactlyInAnyOrder(PACKAGE_A, PACKAGE_B); + } + + /** + * Regression guard for the single-value case: keep behavior identical to before + * the fix when only one package is declared. + */ + @DisplayName("single-value scanBasePackages keeps the sole package entry") + @Test + void testSingleValueScanBasePackages() { + // Arrange + SwaggerContext.setAttribute(MAIN_CLASS_KEY, SinglePackageApplication.class); + + // Act + GroupedOpenApi groupedOpenApi = autoConfiguration.polarisGroupedOpenApi(properties); + + // Assert + assertThat(groupedOpenApi.getPackagesToScan()).containsExactly(PACKAGE_A); + } + + /** + * Externally-configured {@code spring.cloud.polaris.contract.base-package=a,b} + * must also be split into independent entries. + */ + @DisplayName("comma-separated contract base-package property splits into independent entries") + @Test + void testCommaSeparatedBasePackageProperty() { + // Arrange + SwaggerContext.setAttribute(MAIN_CLASS_KEY, SinglePackageApplication.class); + properties.setBasePackage(PACKAGE_A + "," + PACKAGE_B); + + // Act + GroupedOpenApi groupedOpenApi = autoConfiguration.polarisGroupedOpenApi(properties); + + // Assert + assertThat(groupedOpenApi.getPackagesToScan()) + .containsExactlyInAnyOrder(PACKAGE_A, PACKAGE_B); + } + + @SpringBootApplication(scanBasePackages = {PACKAGE_A, PACKAGE_B}) + private static class MultiPackageApplication { + } + + @SpringBootApplication(scanBasePackages = {PACKAGE_A}) + private static class SinglePackageApplication { + } +} diff --git a/spring-cloud-tencent-examples/tsf-example/provider-demo/src/main/java/com/tencent/cloud/tsf/demo/provider/ProviderApplication.java b/spring-cloud-tencent-examples/tsf-example/provider-demo/src/main/java/com/tencent/cloud/tsf/demo/provider/ProviderApplication.java index 008b838d9..97bc24ba7 100644 --- a/spring-cloud-tencent-examples/tsf-example/provider-demo/src/main/java/com/tencent/cloud/tsf/demo/provider/ProviderApplication.java +++ b/spring-cloud-tencent-examples/tsf-example/provider-demo/src/main/java/com/tencent/cloud/tsf/demo/provider/ProviderApplication.java @@ -21,7 +21,7 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.tsf.annotation.EnableTsf; -@SpringBootApplication +@SpringBootApplication(scanBasePackages = {"com.tencent.cloud.tsf.demo.provider", "com.tencent.cloud.tsf"}) @EnableTsf public class ProviderApplication { diff --git a/spring-cloud-tencent-examples/tsf-example/provider-demo/src/main/resources/bootstrap.yml b/spring-cloud-tencent-examples/tsf-example/provider-demo/src/main/resources/bootstrap.yml index 018fc9510..08650f511 100644 --- a/spring-cloud-tencent-examples/tsf-example/provider-demo/src/main/resources/bootstrap.yml +++ b/spring-cloud-tencent-examples/tsf-example/provider-demo/src/main/resources/bootstrap.yml @@ -1,5 +1,7 @@ server: port: 18081 +# servlet: +# context-path: /api spring: application: name: provider-demo