feat: config effective (#1815)

* feat: config effective

Signed-off-by: evelynwei <evelynwei@tencent.com>

* feat: expose config client id in polaris config endpoint

The config SDK context is created in the config-data phase, so its
startup logs may be dropped before the polaris log appenders are ready.
Exposing the client id via the endpoint gives tooling a reliable source
instead of grepping logs (the cloud verify script had to derive it from
the first SDKContext when no stream-establishment anchor log exists).

Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Signed-off-by: evelynwei <evelynwei@tencent.com>
Co-authored-by: evelynwei <evelynwei@tencent.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
pull/1816/head
evelynwei 2 weeks ago committed by GitHub
parent 804f7594dc
commit cae7fe7c91
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

1
.gitignore vendored

@ -59,3 +59,4 @@ CLAUDE.md
/backup /backup
backup backup
*/tls */tls
/.cursor/

@ -14,3 +14,4 @@
- [feat: support using seperated cb in wildcard-api level and add cb counters expire interval config](https://github.com/Tencent/spring-cloud-tencent/pull/1809) - [feat: support using seperated cb in wildcard-api level and add cb counters expire interval config](https://github.com/Tencent/spring-cloud-tencent/pull/1809)
- [feat: adapt to polaris-java ReportClientRequestCustomizer plugin for config watch reporting](https://github.com/Tencent/spring-cloud-tencent/pull/1813) - [feat: adapt to polaris-java ReportClientRequestCustomizer plugin for config watch reporting](https://github.com/Tencent/spring-cloud-tencent/pull/1813)
- [feat: support audit log](https://github.com/Tencent/spring-cloud-tencent/pull/1812) - [feat: support audit log](https://github.com/Tencent/spring-cloud-tencent/pull/1812)
- [feat: support config effective value ](https://github.com/Tencent/spring-cloud-tencent/pull/1815)

@ -32,6 +32,7 @@ import com.tencent.cloud.polaris.context.config.PolarisContextProperties;
import com.tencent.polaris.api.config.consumer.OutlierDetectionConfig; import com.tencent.polaris.api.config.consumer.OutlierDetectionConfig;
import com.tencent.polaris.api.utils.CollectionUtils; import com.tencent.polaris.api.utils.CollectionUtils;
import com.tencent.polaris.api.utils.StringUtils; import com.tencent.polaris.api.utils.StringUtils;
import com.tencent.polaris.configuration.client.internal.ConfigEffectiveQueryConfig;
import com.tencent.polaris.configuration.client.internal.ConfigWatchReportRequestCustomizer; import com.tencent.polaris.configuration.client.internal.ConfigWatchReportRequestCustomizer;
import com.tencent.polaris.configuration.client.internal.ConfigWatchReportRequestCustomizerConfig; import com.tencent.polaris.configuration.client.internal.ConfigWatchReportRequestCustomizerConfig;
import com.tencent.polaris.factory.config.ConfigurationImpl; import com.tencent.polaris.factory.config.ConfigurationImpl;
@ -76,6 +77,14 @@ public class ConfigurationModifier implements PolarisConfigurationConfigModifier
ConfigWatchReportRequestCustomizer.NAME, ConfigWatchReportRequestCustomizerConfig.class); ConfigWatchReportRequestCustomizer.NAME, ConfigWatchReportRequestCustomizerConfig.class);
configWatchCustomizerConfig.setEnable(polarisConfigProperties.getReport().isEnabled()); configWatchCustomizerConfig.setEnable(polarisConfigProperties.getReport().isEnabled());
customizerConfig.setPluginConfig(ConfigWatchReportRequestCustomizer.NAME, configWatchCustomizerConfig); customizerConfig.setPluginConfig(ConfigWatchReportRequestCustomizer.NAME, configWatchCustomizerConfig);
// enabled by default like the config watch report, and cascaded under report.enabled:
// disabling the whole report switch also disables the effective-time query channel
ConfigEffectiveQueryConfig configEffectiveConfig = customizerConfig.getPluginConfig(
ConfigEffectiveQueryConfig.NAME, ConfigEffectiveQueryConfig.class);
boolean effectiveEnabled = polarisConfigProperties.getReport().isEnabled()
&& polarisConfigProperties.getReport().getEffective().isEnabled();
configEffectiveConfig.setEnable(effectiveEnabled);
customizerConfig.setPluginConfig(ConfigEffectiveQueryConfig.NAME, configEffectiveConfig);
configuration.getConsumer().getOutlierDetection().setWhen(OutlierDetectionConfig.When.never); configuration.getConsumer().getOutlierDetection().setWhen(OutlierDetectionConfig.When.never);
configuration.getConsumer().getCircuitBreaker().setEnable(false); configuration.getConsumer().getCircuitBreaker().setEnable(false);

@ -0,0 +1,70 @@
/*
* 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.config;
import com.tencent.cloud.polaris.config.adapter.SpringConfigEffectiveValueProvider;
import com.tencent.polaris.configuration.api.core.ConfigEffectiveValueRegistration;
import com.tencent.polaris.configuration.api.core.ConfigFileService;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.ConfigurableEnvironment;
/**
* Registers the Spring-Environment-based effective value provider into polaris-java
* for config effective-time realtime query.
* <p>
* This auto-configuration is intentionally NOT registered as a BootstrapConfiguration:
* in legacy bootstrap mode the bootstrap context's Environment only carries bootstrap.yml
* sources, so a provider registered there would resolve stale or missing effective values.
* Being main-context-only guarantees the provider always captures the Environment the
* application actually reads. In legacy bootstrap mode the {@link ConfigFileService} bean
* is inherited from the bootstrap (parent) context.
*
* @author evelynwei
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnPolarisConfigEnabled
@AutoConfigureAfter(PolarisConfigBootstrapAutoConfiguration.class)
public class PolarisConfigEffectiveValueAutoConfiguration {
/**
* Enabled by default (aligned with the config watch report switch); set
* spring.cloud.polaris.config.report.effective.enabled=false to opt out. The returned
* registration is closed on context shutdown so the SDK never holds a destroyed
* Environment.
* <p>
* {@code @AutoConfigureAfter} on this class guarantees the {@code configFileService}
* bean definition of {@link PolarisConfigBootstrapAutoConfiguration} is already
* processed, so {@code @ConditionalOnBean} sees it in ConfigData mode; in legacy
* bootstrap mode the bean is visible in the parent context.
*/
@Bean(destroyMethod = "close")
@ConditionalOnBean(ConfigFileService.class)
@ConditionalOnMissingBean
@ConditionalOnProperty(prefix = "spring.cloud.polaris.config.report.effective", name = "enabled",
havingValue = "true", matchIfMissing = true)
public ConfigEffectiveValueRegistration polarisConfigEffectiveValueRegistration(
ConfigurableEnvironment environment, ConfigFileService configFileService) {
return configFileService.registerEffectiveValueProvider(new SpringConfigEffectiveValueProvider(environment));
}
}

@ -0,0 +1,288 @@
/*
* 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.config.adapter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import com.tencent.polaris.configuration.api.core.ConfigEffectiveValueProvider;
import com.tencent.polaris.configuration.api.core.ConfigFileMetadata;
import com.tencent.polaris.configuration.api.core.ConfigKVFile;
import com.tencent.polaris.configuration.api.core.ConfigKeyConflict;
import com.tencent.polaris.configuration.api.core.EffectiveValue;
import com.tencent.polaris.configuration.client.internal.CompositeConfigFile;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cloud.bootstrap.config.BootstrapPropertySource;
import org.springframework.core.env.CompositePropertySource;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.PropertySource;
/**
* Resolves key list, file value, effective value and conflict context from Spring
* {@link ConfigurableEnvironment}, for polaris-java config effective-time query.
* <p>
* Config values must never be written to any log (including DEBUG): exception messages
* and stack traces may embed raw values (e.g. Spring's unresolvable-placeholder error
* quotes the whole value), so logs only carry key names, file coordinates and exception
* class names. Per the interface contract, methods must not throw: single-key failures
* degrade that key only.
*
* @author evelynwei
*/
public class SpringConfigEffectiveValueProvider implements ConfigEffectiveValueProvider {
private static final Logger LOG = LoggerFactory.getLogger(SpringConfigEffectiveValueProvider.class);
private static final String SOURCE_PREFIX = "polaris:";
/**
* Name of Spring Boot's configuration-properties facade source
* ({@code ConfigurationPropertySourcesPropertySource}). Attached at the head of the
* Environment, its containsProperty delegates to every underlying source, so it
* matches any key and would shadow the real source.
*/
private static final String CONFIGURATION_PROPERTIES_SOURCE_NAME = "configurationProperties";
private final ConfigurableEnvironment environment;
public SpringConfigEffectiveValueProvider(ConfigurableEnvironment environment) {
this.environment = environment;
}
@Override
public List<String> getKeys(ConfigFileMetadata configFile) {
try {
ConfigKVFile file = findConfigKVFile(configFile);
if (file == null) {
return Collections.emptyList();
}
List<String> keys = new ArrayList<>(file.getPropertyNames());
// Sort for stable output, friendly to tests and console display.
Collections.sort(keys);
return keys;
}
catch (Throwable t) {
LOG.warn("[SCT Config] Get keys failed, file = {}, error = {}", coordinateOf(configFile),
t.getClass().getSimpleName());
return Collections.emptyList();
}
}
@Override
public EffectiveValue resolve(String key, ConfigFileMetadata configFile) {
String fileValue;
try {
ConfigKVFile file = findConfigKVFile(configFile);
// Raw value in the file (may contain unresolved placeholders).
fileValue = file == null ? null : file.getProperty(key, null);
}
catch (Throwable t) {
// Contract: return null instead of throwing; SDK degrades this key only.
LOG.warn("[SCT Config] Resolve file value failed, key = {}, file = {}, error = {}", key,
coordinateOf(configFile), t.getClass().getSimpleName());
return null;
}
String effectiveValue = null;
String propertySource = null;
try {
// Effective value: Environment has already converged by precedence and
// resolves ${} placeholders, i.e. the value the application actually reads.
effectiveValue = environment.getProperty(key);
propertySource = resolvePropertySourceName(key);
}
catch (Throwable t) {
// Only degrade the effective-value dimension; the file value already in hand is returned.
LOG.warn("[SCT Config] Resolve effective value failed, key = {}, error = {}", key,
t.getClass().getSimpleName());
}
return new EffectiveValue(fileValue, effectiveValue, propertySource);
}
@Override
public List<ConfigKeyConflict> resolveConflicts(String key, ConfigFileMetadata excludeFile) {
List<ConfigKeyConflict> conflicts = new ArrayList<>();
// Static call: PolarisPropertySourceManager is not a bean and cannot be injected.
// Used here only to enumerate the watched-file set, never for precedence.
for (PolarisPropertySource source : PolarisPropertySourceManager.getAllPropertySources()) {
for (ConfigKVFile file : expandSubs(source.getConfigKVFile())) {
try {
collectIfConflict(conflicts, key, file, excludeFile);
}
catch (Throwable t) {
// Per-file fallback: one broken file must not drop conflicts already collected.
LOG.warn("[SCT Config] Resolve conflicts failed, key = {}, error = {}", key,
t.getClass().getSimpleName());
}
}
}
return conflicts;
}
/**
* Walks the ordered PropertySource chain of the Environment and returns the source
* identity of the first one containing the key. Iteration order of
* MutablePropertySources is exactly Spring's precedence order.
*/
private String resolvePropertySourceName(String key) {
for (PropertySource<?> source : environment.getPropertySources()) {
String name = matchSource(source, key);
if (name != null) {
return name;
}
}
return null;
}
/**
* Matches the key inside a single PropertySource, unwrapping wrappers first:
* in bootstrap mode polaris sources are wrapped into a CompositePropertySource
* ("polaris-config") and then into BootstrapPropertySource, so recursion is needed
* to reach the real PolarisPropertySource. Polaris sources are translated to a
* normalized coordinate {@code polaris:namespace/group/fileName}.
*/
private String matchSource(PropertySource<?> source, String key) {
if (CONFIGURATION_PROPERTIES_SOURCE_NAME.equals(source.getName())) {
return null;
}
if (source instanceof CompositePropertySource) {
for (PropertySource<?> sub : ((CompositePropertySource) source).getPropertySources()) {
String name = matchSource(sub, key);
if (name != null) {
return name;
}
}
return null;
}
if (source instanceof BootstrapPropertySource) {
return matchSource(((BootstrapPropertySource<?>) source).getDelegate(), key);
}
if (!source.containsProperty(key)) {
return null;
}
if (source instanceof PolarisPropertySource) {
PolarisPropertySource polarisSource = (PolarisPropertySource) source;
ConfigKVFile file = polarisSource.getConfigKVFile();
if (file instanceof CompositeConfigFile) {
// Group-dimension load: merge semantics is first-file-wins, so the first sub
// file containing the key is the effective one.
for (ConfigKVFile sub : ((CompositeConfigFile) file).getConfigKVFiles()) {
if (sub.getProperty(key, null) != null) {
return formatCoordinate(sub);
}
}
// Fallback: the composite's sub list is frozen at startup and does not cover
// files added to the group at runtime; look them up by group coordinate.
ConfigKVFile added = findInGroup(polarisSource.getNamespace(), polarisSource.getGroup(), key);
if (added != null) {
return formatCoordinate(added);
}
}
else {
return formatCoordinate(file);
}
}
return source.getName();
}
/**
* Finds the watched ConfigKVFile by coordinate. Sources loaded by file dimension match
* directly; sources loaded by group dimension carry an empty fileName and their
* CompositeConfigFile must be expanded to match sub files.
*/
private ConfigKVFile findConfigKVFile(ConfigFileMetadata metadata) {
for (PolarisPropertySource source : PolarisPropertySourceManager.getAllPropertySources()) {
for (ConfigKVFile file : expandSubs(source.getConfigKVFile())) {
if (sameFile(file, metadata)) {
return file;
}
}
}
return null;
}
/**
* Finds a watched file in the given namespace/group containing the key. Used as the
* fallback for group sources whose frozen composite misses runtime-added files.
*/
private ConfigKVFile findInGroup(String namespace, String group, String key) {
for (PolarisPropertySource source : PolarisPropertySourceManager.getAllPropertySources()) {
for (ConfigKVFile file : expandSubs(source.getConfigKVFile())) {
if (Objects.equals(file.getNamespace(), namespace) && Objects.equals(file.getFileGroup(), group)
&& file.getProperty(key, null) != null) {
return file;
}
}
}
return null;
}
/**
* Expands a CompositeConfigFile to its sub files; a plain file expands to itself.
*/
private List<ConfigKVFile> expandSubs(ConfigKVFile file) {
if (file instanceof CompositeConfigFile) {
List<ConfigKVFile> subs = ((CompositeConfigFile) file).getConfigKVFiles();
return subs == null ? Collections.emptyList() : subs;
}
return Collections.singletonList(file);
}
private void collectIfConflict(List<ConfigKeyConflict> conflicts, String key, ConfigKVFile candidate,
ConfigFileMetadata excludeFile) {
if (sameFile(candidate, excludeFile)) {
return;
}
String value = candidate.getProperty(key, null);
if (value == null) {
return;
}
// The same file may be watched at both file dimension and group dimension; dedupe by coordinate.
for (ConfigKeyConflict existing : conflicts) {
if (Objects.equals(existing.getNamespace(), candidate.getNamespace())
&& Objects.equals(existing.getGroup(), candidate.getFileGroup())
&& Objects.equals(existing.getFileName(), candidate.getFileName())) {
return;
}
}
// Coordinate + value only; never the full content of the conflict file.
conflicts.add(new ConfigKeyConflict(candidate.getNamespace(), candidate.getFileGroup(),
candidate.getFileName(), value));
}
private boolean sameFile(ConfigKVFile candidate, ConfigFileMetadata metadata) {
return metadata != null
&& Objects.equals(candidate.getNamespace(), metadata.getNamespace())
&& Objects.equals(candidate.getFileGroup(), metadata.getFileGroup())
&& Objects.equals(candidate.getFileName(), metadata.getFileName());
}
private String formatCoordinate(ConfigKVFile file) {
return SOURCE_PREFIX + file.getNamespace() + "/" + file.getFileGroup() + "/" + file.getFileName();
}
private String coordinateOf(ConfigFileMetadata metadata) {
if (metadata == null) {
return "null";
}
return metadata.getNamespace() + "/" + metadata.getFileGroup() + "/" + metadata.getFileName();
}
}

@ -275,6 +275,11 @@ public class PolarisConfigProperties {
*/ */
private boolean enabled = true; private boolean enabled = true;
/**
* Config effective-time realtime query settings.
*/
private Effective effective = new Effective();
public boolean isEnabled() { public boolean isEnabled() {
return enabled; return enabled;
} }
@ -283,11 +288,48 @@ public class PolarisConfigProperties {
this.enabled = enabled; this.enabled = enabled;
} }
public Effective getEffective() {
return effective;
}
public void setEffective(Effective effective) {
this.effective = effective;
}
@Override @Override
public String toString() { public String toString() {
return "Report{" + return "Report{" +
"enabled=" + enabled + "enabled=" + enabled +
", effective=" + effective +
'}'; '}';
} }
/**
* Config effective-time realtime query settings.
*/
public static class Effective {
/**
* Whether to enable config effective-time realtime query.
* Default true, aligned with the config watch metadata report switch.
* Effective only when {@link Report#enabled} is also true (cascaded).
*/
private boolean enabled = true;
public boolean isEnabled() {
return enabled;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
@Override
public String toString() {
return "Effective{" +
"enabled=" + enabled +
'}';
}
}
} }
} }

@ -21,6 +21,7 @@ import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import com.tencent.cloud.polaris.config.PolarisConfigSDKContextManager;
import com.tencent.cloud.polaris.config.adapter.PolarisPropertySource; import com.tencent.cloud.polaris.config.adapter.PolarisPropertySource;
import com.tencent.cloud.polaris.config.adapter.PolarisPropertySourceManager; import com.tencent.cloud.polaris.config.adapter.PolarisPropertySourceManager;
import com.tencent.cloud.polaris.config.config.PolarisConfigProperties; import com.tencent.cloud.polaris.config.config.PolarisConfigProperties;
@ -50,6 +51,22 @@ public class PolarisConfigEndpoint {
List<PolarisPropertySource> propertySourceList = PolarisPropertySourceManager.getAllPropertySources(); List<PolarisPropertySource> propertySourceList = PolarisPropertySourceManager.getAllPropertySources();
configInfo.put("PolarisPropertySource", propertySourceList); configInfo.put("PolarisPropertySource", propertySourceList);
configInfo.put("ClientId", getClientId());
return configInfo; return configInfo;
} }
/**
* The config SDK context is created in the config-data phase, so its startup logs may be
* dropped before the polaris log appenders are ready. Exposing the client id here gives
* tooling a reliable source instead of grepping logs.
*/
private String getClientId() {
try {
return PolarisConfigSDKContextManager.innerGetConfigSDKContext().getValueContext().getClientId();
}
catch (Throwable throwable) {
return null;
}
}
} }

@ -92,6 +92,13 @@
"description": "Whether to report config watch metadata through the Polaris client reporter.", "description": "Whether to report config watch metadata through the Polaris client reporter.",
"sourceType": "com.tencent.cloud.polaris.config.config.PolarisConfigProperties" "sourceType": "com.tencent.cloud.polaris.config.config.PolarisConfigProperties"
}, },
{
"name": "spring.cloud.polaris.config.report.effective.enabled",
"type": "java.lang.Boolean",
"defaultValue": true,
"description": "Whether to enable config effective-time realtime query. Default true and cascaded under spring.cloud.polaris.config.report.enabled (disabling the whole report switch also disables this query channel).",
"sourceType": "com.tencent.cloud.polaris.config.config.PolarisConfigProperties"
},
{ {
"name": "spring.cloud.polaris.config.crypto.enabled", "name": "spring.cloud.polaris.config.crypto.enabled",
"type": "java.lang.Boolean", "type": "java.lang.Boolean",

@ -1,4 +1,5 @@
com.tencent.cloud.polaris.config.PolarisConfigAutoConfiguration com.tencent.cloud.polaris.config.PolarisConfigAutoConfiguration
com.tencent.cloud.polaris.config.endpoint.PolarisConfigEndpointAutoConfiguration com.tencent.cloud.polaris.config.endpoint.PolarisConfigEndpointAutoConfiguration
com.tencent.cloud.polaris.config.PolarisConfigBootstrapAutoConfiguration com.tencent.cloud.polaris.config.PolarisConfigBootstrapAutoConfiguration
com.tencent.cloud.polaris.config.PolarisConfigEffectiveValueAutoConfiguration
com.tencent.cloud.polaris.config.tsf.PolarisAdaptorTsfConfigAutoConfiguration com.tencent.cloud.polaris.config.tsf.PolarisAdaptorTsfConfigAutoConfiguration

@ -32,6 +32,7 @@ import com.tencent.cloud.polaris.config.config.PolarisConfigProperties;
import com.tencent.cloud.polaris.config.config.PolarisCryptoConfigProperties; import com.tencent.cloud.polaris.config.config.PolarisCryptoConfigProperties;
import com.tencent.cloud.polaris.context.config.PolarisContextProperties; import com.tencent.cloud.polaris.context.config.PolarisContextProperties;
import com.tencent.polaris.api.config.consumer.OutlierDetectionConfig; import com.tencent.polaris.api.config.consumer.OutlierDetectionConfig;
import com.tencent.polaris.configuration.client.internal.ConfigEffectiveQueryConfig;
import com.tencent.polaris.configuration.client.internal.ConfigWatchReportRequestCustomizer; import com.tencent.polaris.configuration.client.internal.ConfigWatchReportRequestCustomizer;
import com.tencent.polaris.configuration.client.internal.ConfigWatchReportRequestCustomizerConfig; import com.tencent.polaris.configuration.client.internal.ConfigWatchReportRequestCustomizerConfig;
import com.tencent.polaris.factory.config.ConfigurationImpl; import com.tencent.polaris.factory.config.ConfigurationImpl;
@ -81,12 +82,18 @@ class ConfigurationModifierTest {
@Mock @Mock
private PolarisConfigProperties.Report report; private PolarisConfigProperties.Report report;
@Mock
private PolarisConfigProperties.Report.Effective effective;
@Mock @Mock
private PolarisContextProperties polarisContextProperties; private PolarisContextProperties polarisContextProperties;
@Mock @Mock
private ConfigWatchReportRequestCustomizerConfig configWatchCustomizer; private ConfigWatchReportRequestCustomizerConfig configWatchCustomizer;
@Mock
private ConfigEffectiveQueryConfig configEffectiveCustomizer;
private ConfigurationModifier configurationModifier; private ConfigurationModifier configurationModifier;
@BeforeEach @BeforeEach
@ -95,6 +102,8 @@ class ConfigurationModifierTest {
polarisConfigProperties, polarisCryptoConfigProperties, polarisContextProperties); polarisConfigProperties, polarisCryptoConfigProperties, polarisContextProperties);
Mockito.lenient().when(polarisConfigProperties.getReport()).thenReturn(report); Mockito.lenient().when(polarisConfigProperties.getReport()).thenReturn(report);
Mockito.lenient().when(report.isEnabled()).thenReturn(true); Mockito.lenient().when(report.isEnabled()).thenReturn(true);
Mockito.lenient().when(report.getEffective()).thenReturn(effective);
Mockito.lenient().when(effective.isEnabled()).thenReturn(true);
} }
/** /**
@ -113,6 +122,8 @@ class ConfigurationModifierTest {
when(globalConfig.getReportClientRequestCustomizer()).thenReturn(requestCustomizer); when(globalConfig.getReportClientRequestCustomizer()).thenReturn(requestCustomizer);
when(requestCustomizer.getPluginConfig(ConfigWatchReportRequestCustomizer.NAME, when(requestCustomizer.getPluginConfig(ConfigWatchReportRequestCustomizer.NAME,
ConfigWatchReportRequestCustomizerConfig.class)).thenReturn(configWatchCustomizer); ConfigWatchReportRequestCustomizerConfig.class)).thenReturn(configWatchCustomizer);
when(requestCustomizer.getPluginConfig(ConfigEffectiveQueryConfig.NAME,
ConfigEffectiveQueryConfig.class)).thenReturn(configEffectiveCustomizer);
Mockito.lenient().when(globalConfig.getServerConnector()).thenReturn(serverConnector); Mockito.lenient().when(globalConfig.getServerConnector()).thenReturn(serverConnector);
Mockito.lenient().when(globalConfig.getAPI()).thenReturn(apiConfig); Mockito.lenient().when(globalConfig.getAPI()).thenReturn(apiConfig);
when(configuration.getGlobal()).thenReturn(globalConfig); when(configuration.getGlobal()).thenReturn(globalConfig);
@ -220,6 +231,55 @@ class ConfigurationModifierTest {
verify(configuration.getConfigFile(), never()).getServerConnector(); verify(configuration.getConfigFile(), never()).getServerConnector();
} }
@DisplayName("modify should disable config effective query when configured")
@Test
void testModify_ConfigEffectiveQueryDisabled() {
ConfigurationImpl configuration = buildMockConfiguration();
when(effective.isEnabled()).thenReturn(false);
when(polarisContextProperties.getEnabled()).thenReturn(false);
configurationModifier.modify(configuration);
// an explicit false always overrides the SDK-side default
verify(configEffectiveCustomizer).setEnable(false);
verify(configuration.getGlobal().getReportClientRequestCustomizer())
.setPluginConfig(ConfigEffectiveQueryConfig.NAME, configEffectiveCustomizer);
verify(configuration.getConfigFile(), never()).getServerConnector();
}
@DisplayName("modify should enable config effective query when configured")
@Test
void testModify_ConfigEffectiveQueryEnabled() {
ConfigurationImpl configuration = buildMockConfiguration();
when(effective.isEnabled()).thenReturn(true);
when(polarisContextProperties.getEnabled()).thenReturn(false);
configurationModifier.modify(configuration);
verify(configEffectiveCustomizer).setEnable(true);
verify(configuration.getGlobal().getReportClientRequestCustomizer())
.setPluginConfig(ConfigEffectiveQueryConfig.NAME, configEffectiveCustomizer);
verify(configuration.getConfigFile(), never()).getServerConnector();
}
@DisplayName("modify should cascade: report disabled also disables config effective query")
@Test
void testModify_ConfigEffectiveQueryCascadesWithReportDisabled() {
ConfigurationImpl configuration = buildMockConfiguration();
// report.enabled=false 级联关闭 effective query,即使 effective.enabled 显式为 true
// (级联短路后 effective.isEnabled() 不再被读取,故用 lenient)
when(report.isEnabled()).thenReturn(false);
Mockito.lenient().when(effective.isEnabled()).thenReturn(true);
when(polarisContextProperties.getEnabled()).thenReturn(false);
configurationModifier.modify(configuration);
verify(configEffectiveCustomizer).setEnable(false);
verify(configuration.getGlobal().getReportClientRequestCustomizer())
.setPluginConfig(ConfigEffectiveQueryConfig.NAME, configEffectiveCustomizer);
verify(configuration.getConfigFile(), never()).getServerConnector();
}
/** /**
* Test modify with local file data source. * Test modify with local file data source.
* Scenario: dataSource is "localFile", both polaris and config are enabled. * Scenario: dataSource is "localFile", both polaris and config are enabled.

@ -0,0 +1,97 @@
/*
* 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.config;
import com.tencent.cloud.polaris.config.adapter.SpringConfigEffectiveValueProvider;
import com.tencent.polaris.configuration.api.core.ConfigEffectiveValueProvider;
import com.tencent.polaris.configuration.api.core.ConfigEffectiveValueRegistration;
import com.tencent.polaris.configuration.api.core.ConfigFileService;
import com.tencent.polaris.configuration.api.core.EffectiveValue;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
/**
* Test for {@link PolarisConfigEffectiveValueAutoConfiguration}. The registration bean must
* only be created in the main application context: in legacy bootstrap mode the bootstrap
* context's Environment carries only bootstrap.yml sources, so a provider registered there
* would report stale effective values.
*
* @author evelynwei
*/
// The mocked AutoCloseable registrations are never closed; the real bean is closed via destroyMethod.
@SuppressWarnings("try")
class PolarisConfigEffectiveValueRegistrationTest {
private final ApplicationContextRunner runner = new ApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(PolarisConfigEffectiveValueAutoConfiguration.class));
@Test
void testRegistrationBeanCreatedWhenConfigFileServicePresent() {
ConfigFileService configFileService = mock(ConfigFileService.class);
ConfigEffectiveValueRegistration registration = mock(ConfigEffectiveValueRegistration.class);
when(configFileService.registerEffectiveValueProvider(any(ConfigEffectiveValueProvider.class)))
.thenReturn(registration);
runner.withBean(ConfigFileService.class, () -> configFileService)
.run(context -> {
assertThat(context).hasSingleBean(ConfigEffectiveValueRegistration.class);
verify(configFileService).registerEffectiveValueProvider(
any(SpringConfigEffectiveValueProvider.class));
});
}
@Test
void testRegistrationBeanSkippedWhenConfigFileServiceAbsent() {
// local data source etc.: no ConfigFileService bean, provider must not be registered
runner.run(context -> assertThat(context).doesNotHaveBean(ConfigEffectiveValueRegistration.class));
}
@Test
void testRegistrationBeanSkippedWhenExplicitlyDisabled() {
ConfigFileService configFileService = mock(ConfigFileService.class);
runner.withBean(ConfigFileService.class, () -> configFileService)
.withPropertyValues("spring.cloud.polaris.config.report.effective.enabled=false")
.run(context -> assertThat(context).doesNotHaveBean(ConfigEffectiveValueRegistration.class));
}
@Test
void testProviderResolvesAgainstRegisteringContextEnvironment() {
ConfigFileService configFileService = mock(ConfigFileService.class);
ConfigEffectiveValueRegistration registration = mock(ConfigEffectiveValueRegistration.class);
ArgumentCaptor<ConfigEffectiveValueProvider> providerCaptor =
ArgumentCaptor.forClass(ConfigEffectiveValueProvider.class);
when(configFileService.registerEffectiveValueProvider(providerCaptor.capture()))
.thenReturn(registration);
runner.withBean(ConfigFileService.class, () -> configFileService)
.withPropertyValues("sct.test.key=from-main-environment")
.run(context -> {
EffectiveValue value = providerCaptor.getValue().resolve("sct.test.key", null);
assertThat(value.getEffectiveValue()).isEqualTo("from-main-environment");
});
}
}

@ -0,0 +1,313 @@
/*
* 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.config.adapter;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import com.tencent.polaris.configuration.api.core.ConfigFileMetadata;
import com.tencent.polaris.configuration.api.core.ConfigKVFile;
import com.tencent.polaris.configuration.api.core.ConfigKeyConflict;
import com.tencent.polaris.configuration.api.core.EffectiveValue;
import com.tencent.polaris.configuration.client.internal.CompositeConfigFile;
import com.tencent.polaris.configuration.client.internal.DefaultConfigFileMetadata;
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.mockito.junit.jupiter.MockitoExtension;
import org.springframework.cloud.bootstrap.config.BootstrapPropertySource;
import org.springframework.core.env.CompositePropertySource;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.MapPropertySource;
import org.springframework.core.env.StandardEnvironment;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.lenient;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
/**
* Test for {@link SpringConfigEffectiveValueProvider}.
*
* @author evelynwei
*/
@ExtendWith(MockitoExtension.class)
class SpringConfigEffectiveValueProviderTest {
private static final String NAMESPACE = "default";
private static final String GROUP = "order-service";
private static final String FILE_NAME = "application.yaml";
private StandardEnvironment environment;
private SpringConfigEffectiveValueProvider provider;
@BeforeEach
void setUp() {
PolarisPropertySourceManager.clearPropertySources();
environment = new StandardEnvironment();
provider = new SpringConfigEffectiveValueProvider(environment);
}
@AfterEach
void tearDown() {
PolarisPropertySourceManager.clearPropertySources();
}
/**
* Registers a file-dimension PolarisPropertySource into both the Environment chain
* (at the tail, below system properties like the real runtime) and the static manager.
*/
private PolarisPropertySource addFileSource(String namespace, String group, String fileName,
Map<String, String> props) {
ConfigKVFile kvFile = mockConfigKVFile(namespace, group, fileName, props);
PolarisPropertySource source = new PolarisPropertySource(namespace, group, fileName, kvFile,
new HashMap<String, Object>(props));
PolarisPropertySourceManager.addPropertySource(source);
environment.getPropertySources().addLast(source);
return source;
}
private ConfigKVFile mockConfigKVFile(String namespace, String group, String fileName,
Map<String, String> props) {
ConfigKVFile kvFile = mock(ConfigKVFile.class);
lenient().when(kvFile.getNamespace()).thenReturn(namespace);
lenient().when(kvFile.getFileGroup()).thenReturn(group);
lenient().when(kvFile.getFileName()).thenReturn(fileName);
lenient().when(kvFile.getPropertyNames()).thenReturn(props.keySet());
lenient().when(kvFile.getProperty(anyString(), any())).thenAnswer(invocation -> {
String key = invocation.getArgument(0);
return props.getOrDefault(key, invocation.getArgument(1));
});
return kvFile;
}
private ConfigFileMetadata metadata(String namespace, String group, String fileName) {
return new DefaultConfigFileMetadata(namespace, group, fileName);
}
@Test
void testCommandLineArgsOverridesPolarisValue() {
addFileSource(NAMESPACE, GROUP, FILE_NAME, Collections.singletonMap("server.port", "8080"));
environment.getPropertySources().addFirst(new MapPropertySource("commandLineArgs",
Collections.singletonMap("server.port", "9090")));
EffectiveValue value = provider.resolve("server.port", metadata(NAMESPACE, GROUP, FILE_NAME));
assertThat(value).isNotNull();
assertThat(value.getFileValue()).isEqualTo("8080");
assertThat(value.getEffectiveValue()).isEqualTo("9090");
assertThat(value.getPropertySource()).isEqualTo("commandLineArgs");
}
@Test
void testEffectiveValueResolvesPlaceholder() {
Map<String, String> props = new LinkedHashMap<>();
props.put("server.port", "${http.port:8080}");
props.put("http.port", "8081");
addFileSource(NAMESPACE, GROUP, FILE_NAME, props);
EffectiveValue value = provider.resolve("server.port", metadata(NAMESPACE, GROUP, FILE_NAME));
assertThat(value).isNotNull();
// file value keeps the raw placeholder, effective value is what the application reads
assertThat(value.getFileValue()).isEqualTo("${http.port:8080}");
assertThat(value.getEffectiveValue()).isEqualTo("8081");
// still sourced from the polaris file itself: normalized coordinate
assertThat(value.getPropertySource()).isEqualTo("polaris:default/order-service/application.yaml");
}
@Test
void testGetKeysSortedAndComplete() {
Map<String, String> props = new LinkedHashMap<>();
props.put("b.key", "2");
props.put("a.key", "1");
props.put("c.key", "3");
addFileSource(NAMESPACE, GROUP, FILE_NAME, props);
List<String> keys = provider.getKeys(metadata(NAMESPACE, GROUP, FILE_NAME));
assertThat(keys).containsExactly("a.key", "b.key", "c.key");
// unknown coordinate: empty list, and SDK will omit the whole properties field
assertThat(provider.getKeys(metadata(NAMESPACE, GROUP, "unknown.yaml"))).isEmpty();
}
@Test
void testConflictsAcrossMultipleWatchedFiles() {
addFileSource(NAMESPACE, GROUP, FILE_NAME, Collections.singletonMap("k", "1"));
addFileSource(NAMESPACE, "common", "common.yaml", Collections.singletonMap("k", "2"));
List<ConfigKeyConflict> conflicts = provider.resolveConflicts("k", metadata(NAMESPACE, GROUP, FILE_NAME));
assertThat(conflicts).hasSize(1);
ConfigKeyConflict conflict = conflicts.get(0);
assertThat(conflict.getNamespace()).isEqualTo(NAMESPACE);
assertThat(conflict.getGroup()).isEqualTo("common");
assertThat(conflict.getFileName()).isEqualTo("common.yaml");
assertThat(conflict.getValue()).isEqualTo("2");
// querying from the other side excludes itself and reports application.yaml
List<ConfigKeyConflict> reverse = provider.resolveConflicts("k", metadata(NAMESPACE, "common", "common.yaml"));
assertThat(reverse).hasSize(1);
assertThat(reverse.get(0).getFileName()).isEqualTo(FILE_NAME);
assertThat(reverse.get(0).getValue()).isEqualTo("1");
}
@Test
void testCompositeConfigFileExpanded() {
// group-dimension loading: sub files merged into a CompositeConfigFile, first file wins
ConfigKVFile subA = mockConfigKVFile(NAMESPACE, "mygroup", "a.yaml", Collections.singletonMap("k", "1"));
ConfigKVFile subB = mockConfigKVFile(NAMESPACE, "mygroup", "b.yaml", Collections.singletonMap("k", "2"));
CompositeConfigFile composite = new CompositeConfigFile(Arrays.asList(subA, subB));
Map<String, Object> merged = new HashMap<>();
merged.put("k", "1");
PolarisPropertySource groupSource = new PolarisPropertySource(NAMESPACE, "mygroup", "", composite, merged);
PolarisPropertySourceManager.addPropertySource(groupSource);
environment.getPropertySources().addLast(groupSource);
// query the losing sub file b.yaml
EffectiveValue value = provider.resolve("k", metadata(NAMESPACE, "mygroup", "b.yaml"));
assertThat(value).isNotNull();
// file value from sub file b, effective value from the merged group map (a wins)
assertThat(value.getFileValue()).isEqualTo("2");
assertThat(value.getEffectiveValue()).isEqualTo("1");
// property source points to the winning sub file, not the opaque group source name
assertThat(value.getPropertySource()).isEqualTo("polaris:default/mygroup/a.yaml");
// conflicts are sub-file grained: b is excluded, a is reported
List<ConfigKeyConflict> conflicts = provider.resolveConflicts("k", metadata(NAMESPACE, "mygroup", "b.yaml"));
assertThat(conflicts).hasSize(1);
assertThat(conflicts.get(0).getFileName()).isEqualTo("a.yaml");
assertThat(conflicts.get(0).getValue()).isEqualTo("1");
// getKeys finds the sub file inside the composite
assertThat(provider.getKeys(metadata(NAMESPACE, "mygroup", "a.yaml"))).containsExactly("k");
}
@Test
void testResolveKeepsFileValueWhenEffectiveResolutionFails() {
// environment.getProperty 抛异常(如不可解析占位符):生效值维度降级为 null,
// 但已到手的文件原始值照常返回
addFileSource(NAMESPACE, GROUP, FILE_NAME, Collections.singletonMap("k", "file-v"));
ConfigurableEnvironment broken = mock(ConfigurableEnvironment.class);
when(broken.getProperty(anyString()))
.thenThrow(new IllegalArgumentException("Could not resolve placeholder"));
SpringConfigEffectiveValueProvider failingProvider = new SpringConfigEffectiveValueProvider(broken);
EffectiveValue value = failingProvider.resolve("k", metadata(NAMESPACE, GROUP, FILE_NAME));
assertThat(value).isNotNull();
assertThat(value.getFileValue()).isEqualTo("file-v");
assertThat(value.getEffectiveValue()).isNull();
assertThat(value.getPropertySource()).isNull();
}
@Test
void testConflictsSkipsBrokenFile() {
// 一个坏文件(getProperty 抛异常)不应丢弃已从健康文件收集的冲突
addFileSource(NAMESPACE, "common", "common.yaml", Collections.singletonMap("k", "2"));
ConfigKVFile broken = mock(ConfigKVFile.class);
lenient().when(broken.getNamespace()).thenReturn(NAMESPACE);
lenient().when(broken.getFileGroup()).thenReturn("broken-group");
lenient().when(broken.getFileName()).thenReturn("broken.yaml");
when(broken.getProperty(anyString(), any())).thenThrow(new RuntimeException("boom"));
PolarisPropertySource brokenSource = new PolarisPropertySource(NAMESPACE, "broken-group", "broken.yaml",
broken, new HashMap<String, Object>());
PolarisPropertySourceManager.addPropertySource(brokenSource);
List<ConfigKeyConflict> conflicts = provider.resolveConflicts("k", metadata(NAMESPACE, GROUP, FILE_NAME));
assertThat(conflicts).hasSize(1);
assertThat(conflicts.get(0).getFileName()).isEqualTo("common.yaml");
assertThat(conflicts.get(0).getValue()).isEqualTo("2");
}
@Test
void testConflictsDedupWhenWatchedAtBothDimensions() {
// 同一文件同时以文件维度与 group 维度监听注册:冲突按坐标去重
Map<String, String> props = Collections.singletonMap("k", "2");
addFileSource(NAMESPACE, "common", "common.yaml", props);
ConfigKVFile sub = mockConfigKVFile(NAMESPACE, "common", "common.yaml", props);
CompositeConfigFile composite = new CompositeConfigFile(Collections.singletonList(sub));
PolarisPropertySource groupSource = new PolarisPropertySource(NAMESPACE, "common", "", composite,
new HashMap<String, Object>(props));
PolarisPropertySourceManager.addPropertySource(groupSource);
List<ConfigKeyConflict> conflicts = provider.resolveConflicts("k", metadata(NAMESPACE, GROUP, FILE_NAME));
assertThat(conflicts).hasSize(1);
assertThat(conflicts.get(0).getFileName()).isEqualTo("common.yaml");
assertThat(conflicts.get(0).getValue()).isEqualTo("2");
}
@Test
void testBootstrapWrappedSourcesUnwrapped() {
// bootstrap 模式:PolarisPropertySource 被 CompositePropertySource("polaris-config") 聚合,
// 外层再被 BootstrapPropertySource 包装——来源解析需递归解包出坐标
PolarisPropertySource polarisSource = addFileSource(NAMESPACE, GROUP, FILE_NAME,
Collections.singletonMap("k", "1"));
environment.getPropertySources().remove(polarisSource.getName());
CompositePropertySource composite = new CompositePropertySource("polaris-config");
composite.addPropertySource(polarisSource);
environment.getPropertySources().addLast(new BootstrapPropertySource<>(composite));
EffectiveValue value = provider.resolve("k", metadata(NAMESPACE, GROUP, FILE_NAME));
assertThat(value).isNotNull();
assertThat(value.getEffectiveValue()).isEqualTo("1");
assertThat(value.getPropertySource()).isEqualTo("polaris:default/order-service/application.yaml");
}
@Test
void testConfigurationPropertiesFacadeSourceSkipped() {
// Spring Boot 的 configurationProperties facade source 挂在链头,
// 其 containsProperty 委托给所有底层 source(任意 key 都命中),
// 不跳过会遮蔽真实来源,property_source 恒为 "configurationProperties"
addFileSource(NAMESPACE, GROUP, FILE_NAME, Collections.singletonMap("k", "1"));
environment.getPropertySources().addFirst(new MapPropertySource("configurationProperties",
Collections.singletonMap("k", "1")));
EffectiveValue value = provider.resolve("k", metadata(NAMESPACE, GROUP, FILE_NAME));
assertThat(value).isNotNull();
assertThat(value.getPropertySource()).isEqualTo("polaris:default/order-service/application.yaml");
}
@Test
void testKeyNotPresentAnywhere() {
addFileSource(NAMESPACE, GROUP, FILE_NAME, Collections.singletonMap("other.key", "1"));
EffectiveValue value = provider.resolve("missing.key", metadata(NAMESPACE, GROUP, FILE_NAME));
assertThat(value).isNotNull();
assertThat(value.getFileValue()).isNull();
assertThat(value.getEffectiveValue()).isNull();
assertThat(value.getPropertySource()).isNull();
}
}

@ -21,14 +21,19 @@ import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import com.tencent.cloud.polaris.config.PolarisConfigSDKContextManager;
import com.tencent.cloud.polaris.config.adapter.MockedConfigKVFile; import com.tencent.cloud.polaris.config.adapter.MockedConfigKVFile;
import com.tencent.cloud.polaris.config.adapter.PolarisPropertySource; import com.tencent.cloud.polaris.config.adapter.PolarisPropertySource;
import com.tencent.cloud.polaris.config.adapter.PolarisPropertySourceManager; import com.tencent.cloud.polaris.config.adapter.PolarisPropertySourceManager;
import com.tencent.cloud.polaris.config.config.PolarisConfigProperties; import com.tencent.cloud.polaris.config.config.PolarisConfigProperties;
import com.tencent.polaris.api.plugin.common.ValueContext;
import com.tencent.polaris.client.api.SDKContext;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock; import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.junit.jupiter.MockitoExtension; import org.mockito.junit.jupiter.MockitoExtension;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
@ -53,6 +58,11 @@ public class PolarisConfigEndpointTest {
PolarisPropertySourceManager.clearPropertySources(); PolarisPropertySourceManager.clearPropertySources();
} }
@AfterEach
public void tearDown() {
PolarisConfigSDKContextManager.innerConfigDestroy();
}
@Test @Test
public void testPolarisConfigEndpoint() { public void testPolarisConfigEndpoint() {
Map<String, Object> content = new HashMap<>(); Map<String, Object> content = new HashMap<>();
@ -69,4 +79,24 @@ public class PolarisConfigEndpointTest {
assertThat(polarisConfigProperties).isEqualTo(info.get("PolarisConfigProperties")); assertThat(polarisConfigProperties).isEqualTo(info.get("PolarisConfigProperties"));
assertThat(Collections.singletonList(polarisPropertySource)).isEqualTo(info.get("PolarisPropertySource")); assertThat(Collections.singletonList(polarisPropertySource)).isEqualTo(info.get("PolarisPropertySource"));
} }
@Test
public void testPolarisConfigEndpointExposesClientId() {
SDKContext sdkContext = Mockito.mock(SDKContext.class);
ValueContext valueContext = Mockito.mock(ValueContext.class);
Mockito.when(sdkContext.getValueContext()).thenReturn(valueContext);
Mockito.when(valueContext.getClientId()).thenReturn("host_1234_0");
PolarisConfigSDKContextManager.setConfigSDKContext(sdkContext);
PolarisConfigEndpoint endpoint = new PolarisConfigEndpoint(polarisConfigProperties);
assertThat(endpoint.polarisConfig().get("ClientId")).isEqualTo("host_1234_0");
}
@Test
public void testPolarisConfigEndpointClientIdIsNullWhenContextAbsent() {
PolarisConfigEndpoint endpoint = new PolarisConfigEndpoint(polarisConfigProperties);
assertThat(endpoint.polarisConfig().get("ClientId")).isNull();
}
} }

Loading…
Cancel
Save