Skip to content

[pull] main from spring-projects:main #37

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
May 9, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2025 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.
Expand All @@ -21,6 +21,7 @@
import java.util.Properties;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;

import org.springframework.core.convert.ConverterNotFoundException;
Expand All @@ -38,18 +39,15 @@
*/
class PropertySourcesPropertyResolverTests {

private Properties testProperties;
private final Properties testProperties = new Properties();

private MutablePropertySources propertySources;
private final MutablePropertySources propertySources = new MutablePropertySources();

private PropertySourcesPropertyResolver propertyResolver;
private final PropertySourcesPropertyResolver propertyResolver = new PropertySourcesPropertyResolver(propertySources);


@BeforeEach
void setUp() {
propertySources = new MutablePropertySources();
propertyResolver = new PropertySourcesPropertyResolver(propertySources);
testProperties = new Properties();
propertySources.addFirst(new PropertiesPropertySource("testProperties", testProperties));
}

Expand Down Expand Up @@ -77,14 +75,12 @@ void getProperty_withDefaultValue() {

@Test
void getProperty_propertySourceSearchOrderIsFIFO() {
MutablePropertySources sources = new MutablePropertySources();
PropertyResolver resolver = new PropertySourcesPropertyResolver(sources);
sources.addFirst(new MockPropertySource("ps1").withProperty("pName", "ps1Value"));
assertThat(resolver.getProperty("pName")).isEqualTo("ps1Value");
sources.addFirst(new MockPropertySource("ps2").withProperty("pName", "ps2Value"));
assertThat(resolver.getProperty("pName")).isEqualTo("ps2Value");
sources.addFirst(new MockPropertySource("ps3").withProperty("pName", "ps3Value"));
assertThat(resolver.getProperty("pName")).isEqualTo("ps3Value");
propertySources.addFirst(new MockPropertySource("ps1").withProperty("pName", "ps1Value"));
assertThat(propertyResolver.getProperty("pName")).isEqualTo("ps1Value");
propertySources.addFirst(new MockPropertySource("ps2").withProperty("pName", "ps2Value"));
assertThat(propertyResolver.getProperty("pName")).isEqualTo("ps2Value");
propertySources.addFirst(new MockPropertySource("ps3").withProperty("pName", "ps3Value"));
assertThat(propertyResolver.getProperty("pName")).isEqualTo("ps3Value");
}

@Test
Expand Down Expand Up @@ -115,8 +111,8 @@ void getProperty_withNonConvertibleTargetType() {

class TestType { }

assertThatExceptionOfType(ConverterNotFoundException.class).isThrownBy(() ->
propertyResolver.getProperty("foo", TestType.class));
assertThatExceptionOfType(ConverterNotFoundException.class)
.isThrownBy(() -> propertyResolver.getProperty("foo", TestType.class));
}

@Test
Expand All @@ -127,7 +123,6 @@ void getProperty_doesNotCache_replaceExistingKeyPostConstruction() {

HashMap<String, Object> map = new HashMap<>();
map.put(key, value1); // before construction
MutablePropertySources propertySources = new MutablePropertySources();
propertySources.addFirst(new MapPropertySource("testProperties", map));
PropertyResolver propertyResolver = new PropertySourcesPropertyResolver(propertySources);
assertThat(propertyResolver.getProperty(key)).isEqualTo(value1);
Expand All @@ -138,7 +133,6 @@ void getProperty_doesNotCache_replaceExistingKeyPostConstruction() {
@Test
void getProperty_doesNotCache_addNewKeyPostConstruction() {
HashMap<String, Object> map = new HashMap<>();
MutablePropertySources propertySources = new MutablePropertySources();
propertySources.addFirst(new MapPropertySource("testProperties", map));
PropertyResolver propertyResolver = new PropertySourcesPropertyResolver(propertySources);
assertThat(propertyResolver.getProperty("foo")).isNull();
Expand All @@ -148,10 +142,9 @@ void getProperty_doesNotCache_addNewKeyPostConstruction() {

@Test
void getPropertySources_replacePropertySource() {
propertySources = new MutablePropertySources();
propertyResolver = new PropertySourcesPropertyResolver(propertySources);
propertySources.addLast(new MockPropertySource("local").withProperty("foo", "localValue"));
propertySources.addLast(new MockPropertySource("system").withProperty("foo", "systemValue"));
assertThat(propertySources).hasSize(3);

// 'local' was added first so has precedence
assertThat(propertyResolver.getProperty("foo")).isEqualTo("localValue");
Expand All @@ -162,89 +155,73 @@ void getPropertySources_replacePropertySource() {
// 'system' now has precedence
assertThat(propertyResolver.getProperty("foo")).isEqualTo("newValue");

assertThat(propertySources).hasSize(2);
assertThat(propertySources).hasSize(3);
}

@Test
void getRequiredProperty() {
testProperties.put("exists", "xyz");
assertThat(propertyResolver.getRequiredProperty("exists")).isEqualTo("xyz");

assertThatIllegalStateException().isThrownBy(() ->
propertyResolver.getRequiredProperty("bogus"));
assertThatIllegalStateException().isThrownBy(() -> propertyResolver.getRequiredProperty("bogus"));
}

@Test
void getRequiredProperty_withStringArrayConversion() {
testProperties.put("exists", "abc,123");
assertThat(propertyResolver.getRequiredProperty("exists", String[].class)).isEqualTo(new String[] { "abc", "123" });
assertThat(propertyResolver.getRequiredProperty("exists", String[].class)).containsExactly("abc", "123");

assertThatIllegalStateException().isThrownBy(() ->
propertyResolver.getRequiredProperty("bogus", String[].class));
assertThatIllegalStateException().isThrownBy(() -> propertyResolver.getRequiredProperty("bogus", String[].class));
}

@Test
void resolvePlaceholders() {
MutablePropertySources propertySources = new MutablePropertySources();
propertySources.addFirst(new MockPropertySource().withProperty("key", "value"));
PropertyResolver resolver = new PropertySourcesPropertyResolver(propertySources);
assertThat(resolver.resolvePlaceholders("Replace this ${key}")).isEqualTo("Replace this value");
assertThat(propertyResolver.resolvePlaceholders("Replace this ${key}")).isEqualTo("Replace this value");
}

@Test
void resolvePlaceholders_withUnresolvable() {
MutablePropertySources propertySources = new MutablePropertySources();
propertySources.addFirst(new MockPropertySource().withProperty("key", "value"));
PropertyResolver resolver = new PropertySourcesPropertyResolver(propertySources);
assertThat(resolver.resolvePlaceholders("Replace this ${key} plus ${unknown}"))
assertThat(propertyResolver.resolvePlaceholders("Replace this ${key} plus ${unknown}"))
.isEqualTo("Replace this value plus ${unknown}");
}

@Test
void resolvePlaceholders_withDefaultValue() {
MutablePropertySources propertySources = new MutablePropertySources();
propertySources.addFirst(new MockPropertySource().withProperty("key", "value"));
PropertyResolver resolver = new PropertySourcesPropertyResolver(propertySources);
assertThat(resolver.resolvePlaceholders("Replace this ${key} plus ${unknown:defaultValue}"))
assertThat(propertyResolver.resolvePlaceholders("Replace this ${key} plus ${unknown:defaultValue}"))
.isEqualTo("Replace this value plus defaultValue");
}

@Test
void resolvePlaceholders_withNullInput() {
assertThatIllegalArgumentException().isThrownBy(() ->
new PropertySourcesPropertyResolver(new MutablePropertySources()).resolvePlaceholders(null));
assertThatIllegalArgumentException().isThrownBy(() -> propertyResolver.resolvePlaceholders(null));
}

@Test
void resolveRequiredPlaceholders() {
MutablePropertySources propertySources = new MutablePropertySources();
propertySources.addFirst(new MockPropertySource().withProperty("key", "value"));
PropertyResolver resolver = new PropertySourcesPropertyResolver(propertySources);
assertThat(resolver.resolveRequiredPlaceholders("Replace this ${key}")).isEqualTo("Replace this value");
assertThat(propertyResolver.resolveRequiredPlaceholders("Replace this ${key}")).isEqualTo("Replace this value");
}

@Test
void resolveRequiredPlaceholders_withUnresolvable() {
MutablePropertySources propertySources = new MutablePropertySources();
propertySources.addFirst(new MockPropertySource().withProperty("key", "value"));
PropertyResolver resolver = new PropertySourcesPropertyResolver(propertySources);
assertThatExceptionOfType(PlaceholderResolutionException.class).isThrownBy(() ->
resolver.resolveRequiredPlaceholders("Replace this ${key} plus ${unknown}"));
assertThatExceptionOfType(PlaceholderResolutionException.class)
.isThrownBy(() -> propertyResolver.resolveRequiredPlaceholders("Replace this ${key} plus ${unknown}"));
}

@Test
void resolveRequiredPlaceholders_withDefaultValue() {
MutablePropertySources propertySources = new MutablePropertySources();
propertySources.addFirst(new MockPropertySource().withProperty("key", "value"));
PropertyResolver resolver = new PropertySourcesPropertyResolver(propertySources);
assertThat(resolver.resolveRequiredPlaceholders("Replace this ${key} plus ${unknown:defaultValue}"))
assertThat(propertyResolver.resolveRequiredPlaceholders("Replace this ${key} plus ${unknown:defaultValue}"))
.isEqualTo("Replace this value plus defaultValue");
}

@Test
void resolveRequiredPlaceholders_withNullInput() {
assertThatIllegalArgumentException().isThrownBy(() ->
new PropertySourcesPropertyResolver(new MutablePropertySources()).resolveRequiredPlaceholders(null));
assertThatIllegalArgumentException().isThrownBy(() -> propertyResolver.resolveRequiredPlaceholders(null));
}

@Test
Expand All @@ -256,17 +233,17 @@ void setRequiredProperties_andValidateRequiredProperties() {
propertyResolver.setRequiredProperties("foo", "bar");

// neither foo nor bar properties are present -> validating should throw
assertThatExceptionOfType(MissingRequiredPropertiesException.class).isThrownBy(
propertyResolver::validateRequiredProperties)
.withMessage("The following properties were declared as required " +
"but could not be resolved: [foo, bar]");
assertThatExceptionOfType(MissingRequiredPropertiesException.class)
.isThrownBy(propertyResolver::validateRequiredProperties)
.withMessage("The following properties were declared as required " +
"but could not be resolved: [foo, bar]");

// add foo property -> validation should fail only on missing 'bar' property
testProperties.put("foo", "fooValue");
assertThatExceptionOfType(MissingRequiredPropertiesException.class).isThrownBy(
propertyResolver::validateRequiredProperties)
.withMessage("The following properties were declared as required " +
"but could not be resolved: [bar]");
assertThatExceptionOfType(MissingRequiredPropertiesException.class)
.isThrownBy(propertyResolver::validateRequiredProperties)
.withMessage("The following properties were declared as required " +
"but could not be resolved: [bar]");

// add bar property -> validation should pass, even with an empty string value
testProperties.put("bar", "");
Expand All @@ -291,13 +268,13 @@ void resolveNestedPropertyPlaceholders() {
assertThat(pr.getProperty("p2")).isEqualTo("v2");
assertThat(pr.getProperty("p3")).isEqualTo("v1:v2");
assertThat(pr.getProperty("p4")).isEqualTo("v1:v2");
assertThatExceptionOfType(PlaceholderResolutionException.class).isThrownBy(() ->
pr.getProperty("p5"))
.withMessageContaining("Could not resolve placeholder 'bogus' in value \"${p1}:${p2}:${bogus}\"");
assertThatExceptionOfType(PlaceholderResolutionException.class)
.isThrownBy(() -> pr.getProperty("p5"))
.withMessageContaining("Could not resolve placeholder 'bogus' in value \"${p1}:${p2}:${bogus}\"");
assertThat(pr.getProperty("p6")).isEqualTo("v1:v2:def");
assertThatExceptionOfType(PlaceholderResolutionException.class).isThrownBy(() ->
pr.getProperty("pL"))
.withMessageContaining("Circular");
assertThatExceptionOfType(PlaceholderResolutionException.class)
.isThrownBy(() -> pr.getProperty("pL"))
.withMessageContaining("Circular");
}

@Test
Expand Down Expand Up @@ -349,9 +326,9 @@ void ignoreUnresolvableNestedPlaceholdersIsConfigurable() {

// placeholders nested within the value of "p4" are unresolvable and cause an
// exception by default
assertThatExceptionOfType(PlaceholderResolutionException.class).isThrownBy(() ->
pr.getProperty("p4"))
.withMessageContaining("Could not resolve placeholder 'bogus' in value \"${p1}:${p2}:${bogus}\"");
assertThatExceptionOfType(PlaceholderResolutionException.class)
.isThrownBy(() -> pr.getProperty("p4"))
.withMessageContaining("Could not resolve placeholder 'bogus' in value \"${p1}:${p2}:${bogus}\"");

// relax the treatment of unresolvable nested placeholders
pr.setIgnoreUnresolvableNestedPlaceholders(true);
Expand All @@ -361,9 +338,58 @@ void ignoreUnresolvableNestedPlaceholdersIsConfigurable() {
// resolve[Nested]Placeholders methods behave as usual regardless the value of
// ignoreUnresolvableNestedPlaceholders
assertThat(pr.resolvePlaceholders("${p1}:${p2}:${bogus}")).isEqualTo("v1:v2:${bogus}");
assertThatExceptionOfType(PlaceholderResolutionException.class).isThrownBy(() ->
pr.resolveRequiredPlaceholders("${p1}:${p2}:${bogus}"))
.withMessageContaining("Could not resolve placeholder 'bogus' in value \"${p1}:${p2}:${bogus}\"");
assertThatExceptionOfType(PlaceholderResolutionException.class)
.isThrownBy(() -> pr.resolveRequiredPlaceholders("${p1}:${p2}:${bogus}"))
.withMessageContaining("Could not resolve placeholder 'bogus' in value \"${p1}:${p2}:${bogus}\"");
}


@Nested
class EscapedPlaceholderTests {

@Test // gh-34720
void escapedPlaceholdersAreNotEvaluated() {
testProperties.put("prop1", "value1");
testProperties.put("prop2", "value2\\${prop1}");

assertThat(propertyResolver.getProperty("prop2")).isEqualTo("value2${prop1}");
}

@Test // gh-34720
void escapedPlaceholdersAreNotEvaluatedWithCharSequenceValues() {
testProperties.put("prop1", "value1");
testProperties.put("prop2", new StringBuilder("value2\\${prop1}"));

assertThat(propertyResolver.getProperty("prop2")).isEqualTo("value2${prop1}");
}

@Test // gh-34720
void multipleEscapedPlaceholdersArePreserved() {
testProperties.put("prop1", "value1");
testProperties.put("prop2", "value2");
testProperties.put("complex", "start\\${prop1}middle\\${prop2}end");

assertThat(propertyResolver.getProperty("complex")).isEqualTo("start${prop1}middle${prop2}end");
}

@Test // gh-34720
void doubleBackslashesAreProcessedCorrectly() {
testProperties.put("prop1", "value1");
testProperties.put("doubleEscaped", "value2\\\\${prop1}");

assertThat(propertyResolver.getProperty("doubleEscaped")).isEqualTo("value2\\${prop1}");
}

@Test // gh-34720
void escapedPlaceholdersInNestedPropertiesAreNotEvaluated() {
testProperties.put("p1", "v1");
testProperties.put("p2", "v2");
testProperties.put("escaped", "prefix-\\${p1}");
testProperties.put("nested", "${escaped}-${p2}");

assertThat(propertyResolver.getProperty("nested")).isEqualTo("prefix-${p1}-v2");
}

}

}
Loading