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) <noreply@anthropic.com>
Signed-off-by: Haotian Zhang <928016560@qq.com>
pull/1807/head
Haotian Zhang 6 days ago
parent 68253fd2c7
commit 750a452fe8

@ -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())

@ -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<String> 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 {
}
}

@ -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 {

@ -1,5 +1,7 @@
server:
port: 18081
# servlet:
# context-path: /api
spring:
application:
name: provider-demo

Loading…
Cancel
Save