1
1
/*
2
- * Copyright 2002-2024 the original author or authors.
2
+ * Copyright 2002-2025 the original author or authors.
3
3
*
4
4
* Licensed under the Apache License, Version 2.0 (the "License");
5
5
* you may not use this file except in compliance with the License.
21
21
import java .util .Properties ;
22
22
23
23
import org .junit .jupiter .api .BeforeEach ;
24
+ import org .junit .jupiter .api .Nested ;
24
25
import org .junit .jupiter .api .Test ;
25
26
26
27
import org .springframework .core .convert .ConverterNotFoundException ;
38
39
*/
39
40
class PropertySourcesPropertyResolverTests {
40
41
41
- private Properties testProperties ;
42
+ private final Properties testProperties = new Properties () ;
42
43
43
- private MutablePropertySources propertySources ;
44
+ private final MutablePropertySources propertySources = new MutablePropertySources () ;
44
45
45
- private PropertySourcesPropertyResolver propertyResolver ;
46
+ private final PropertySourcesPropertyResolver propertyResolver = new PropertySourcesPropertyResolver ( propertySources ) ;
46
47
47
48
48
49
@ BeforeEach
49
50
void setUp () {
50
- propertySources = new MutablePropertySources ();
51
- propertyResolver = new PropertySourcesPropertyResolver (propertySources );
52
- testProperties = new Properties ();
53
51
propertySources .addFirst (new PropertiesPropertySource ("testProperties" , testProperties ));
54
52
}
55
53
@@ -77,14 +75,12 @@ void getProperty_withDefaultValue() {
77
75
78
76
@ Test
79
77
void getProperty_propertySourceSearchOrderIsFIFO () {
80
- MutablePropertySources sources = new MutablePropertySources ();
81
- PropertyResolver resolver = new PropertySourcesPropertyResolver (sources );
82
- sources .addFirst (new MockPropertySource ("ps1" ).withProperty ("pName" , "ps1Value" ));
83
- assertThat (resolver .getProperty ("pName" )).isEqualTo ("ps1Value" );
84
- sources .addFirst (new MockPropertySource ("ps2" ).withProperty ("pName" , "ps2Value" ));
85
- assertThat (resolver .getProperty ("pName" )).isEqualTo ("ps2Value" );
86
- sources .addFirst (new MockPropertySource ("ps3" ).withProperty ("pName" , "ps3Value" ));
87
- assertThat (resolver .getProperty ("pName" )).isEqualTo ("ps3Value" );
78
+ propertySources .addFirst (new MockPropertySource ("ps1" ).withProperty ("pName" , "ps1Value" ));
79
+ assertThat (propertyResolver .getProperty ("pName" )).isEqualTo ("ps1Value" );
80
+ propertySources .addFirst (new MockPropertySource ("ps2" ).withProperty ("pName" , "ps2Value" ));
81
+ assertThat (propertyResolver .getProperty ("pName" )).isEqualTo ("ps2Value" );
82
+ propertySources .addFirst (new MockPropertySource ("ps3" ).withProperty ("pName" , "ps3Value" ));
83
+ assertThat (propertyResolver .getProperty ("pName" )).isEqualTo ("ps3Value" );
88
84
}
89
85
90
86
@ Test
@@ -115,8 +111,8 @@ void getProperty_withNonConvertibleTargetType() {
115
111
116
112
class TestType { }
117
113
118
- assertThatExceptionOfType (ConverterNotFoundException .class ). isThrownBy (() ->
119
- propertyResolver .getProperty ("foo" , TestType .class ));
114
+ assertThatExceptionOfType (ConverterNotFoundException .class )
115
+ . isThrownBy (() -> propertyResolver .getProperty ("foo" , TestType .class ));
120
116
}
121
117
122
118
@ Test
@@ -127,7 +123,6 @@ void getProperty_doesNotCache_replaceExistingKeyPostConstruction() {
127
123
128
124
HashMap <String , Object > map = new HashMap <>();
129
125
map .put (key , value1 ); // before construction
130
- MutablePropertySources propertySources = new MutablePropertySources ();
131
126
propertySources .addFirst (new MapPropertySource ("testProperties" , map ));
132
127
PropertyResolver propertyResolver = new PropertySourcesPropertyResolver (propertySources );
133
128
assertThat (propertyResolver .getProperty (key )).isEqualTo (value1 );
@@ -138,7 +133,6 @@ void getProperty_doesNotCache_replaceExistingKeyPostConstruction() {
138
133
@ Test
139
134
void getProperty_doesNotCache_addNewKeyPostConstruction () {
140
135
HashMap <String , Object > map = new HashMap <>();
141
- MutablePropertySources propertySources = new MutablePropertySources ();
142
136
propertySources .addFirst (new MapPropertySource ("testProperties" , map ));
143
137
PropertyResolver propertyResolver = new PropertySourcesPropertyResolver (propertySources );
144
138
assertThat (propertyResolver .getProperty ("foo" )).isNull ();
@@ -148,10 +142,9 @@ void getProperty_doesNotCache_addNewKeyPostConstruction() {
148
142
149
143
@ Test
150
144
void getPropertySources_replacePropertySource () {
151
- propertySources = new MutablePropertySources ();
152
- propertyResolver = new PropertySourcesPropertyResolver (propertySources );
153
145
propertySources .addLast (new MockPropertySource ("local" ).withProperty ("foo" , "localValue" ));
154
146
propertySources .addLast (new MockPropertySource ("system" ).withProperty ("foo" , "systemValue" ));
147
+ assertThat (propertySources ).hasSize (3 );
155
148
156
149
// 'local' was added first so has precedence
157
150
assertThat (propertyResolver .getProperty ("foo" )).isEqualTo ("localValue" );
@@ -162,89 +155,73 @@ void getPropertySources_replacePropertySource() {
162
155
// 'system' now has precedence
163
156
assertThat (propertyResolver .getProperty ("foo" )).isEqualTo ("newValue" );
164
157
165
- assertThat (propertySources ).hasSize (2 );
158
+ assertThat (propertySources ).hasSize (3 );
166
159
}
167
160
168
161
@ Test
169
162
void getRequiredProperty () {
170
163
testProperties .put ("exists" , "xyz" );
171
164
assertThat (propertyResolver .getRequiredProperty ("exists" )).isEqualTo ("xyz" );
172
165
173
- assertThatIllegalStateException ().isThrownBy (() ->
174
- propertyResolver .getRequiredProperty ("bogus" ));
166
+ assertThatIllegalStateException ().isThrownBy (() -> propertyResolver .getRequiredProperty ("bogus" ));
175
167
}
176
168
177
169
@ Test
178
170
void getRequiredProperty_withStringArrayConversion () {
179
171
testProperties .put ("exists" , "abc,123" );
180
- assertThat (propertyResolver .getRequiredProperty ("exists" , String [].class )).isEqualTo ( new String [] { "abc" , "123" } );
172
+ assertThat (propertyResolver .getRequiredProperty ("exists" , String [].class )).containsExactly ( "abc" , "123" );
181
173
182
- assertThatIllegalStateException ().isThrownBy (() ->
183
- propertyResolver .getRequiredProperty ("bogus" , String [].class ));
174
+ assertThatIllegalStateException ().isThrownBy (() -> propertyResolver .getRequiredProperty ("bogus" , String [].class ));
184
175
}
185
176
186
177
@ Test
187
178
void resolvePlaceholders () {
188
- MutablePropertySources propertySources = new MutablePropertySources ();
189
179
propertySources .addFirst (new MockPropertySource ().withProperty ("key" , "value" ));
190
- PropertyResolver resolver = new PropertySourcesPropertyResolver (propertySources );
191
- assertThat (resolver .resolvePlaceholders ("Replace this ${key}" )).isEqualTo ("Replace this value" );
180
+ assertThat (propertyResolver .resolvePlaceholders ("Replace this ${key}" )).isEqualTo ("Replace this value" );
192
181
}
193
182
194
183
@ Test
195
184
void resolvePlaceholders_withUnresolvable () {
196
- MutablePropertySources propertySources = new MutablePropertySources ();
197
185
propertySources .addFirst (new MockPropertySource ().withProperty ("key" , "value" ));
198
- PropertyResolver resolver = new PropertySourcesPropertyResolver (propertySources );
199
- assertThat (resolver .resolvePlaceholders ("Replace this ${key} plus ${unknown}" ))
186
+ assertThat (propertyResolver .resolvePlaceholders ("Replace this ${key} plus ${unknown}" ))
200
187
.isEqualTo ("Replace this value plus ${unknown}" );
201
188
}
202
189
203
190
@ Test
204
191
void resolvePlaceholders_withDefaultValue () {
205
- MutablePropertySources propertySources = new MutablePropertySources ();
206
192
propertySources .addFirst (new MockPropertySource ().withProperty ("key" , "value" ));
207
- PropertyResolver resolver = new PropertySourcesPropertyResolver (propertySources );
208
- assertThat (resolver .resolvePlaceholders ("Replace this ${key} plus ${unknown:defaultValue}" ))
193
+ assertThat (propertyResolver .resolvePlaceholders ("Replace this ${key} plus ${unknown:defaultValue}" ))
209
194
.isEqualTo ("Replace this value plus defaultValue" );
210
195
}
211
196
212
197
@ Test
213
198
void resolvePlaceholders_withNullInput () {
214
- assertThatIllegalArgumentException ().isThrownBy (() ->
215
- new PropertySourcesPropertyResolver (new MutablePropertySources ()).resolvePlaceholders (null ));
199
+ assertThatIllegalArgumentException ().isThrownBy (() -> propertyResolver .resolvePlaceholders (null ));
216
200
}
217
201
218
202
@ Test
219
203
void resolveRequiredPlaceholders () {
220
- MutablePropertySources propertySources = new MutablePropertySources ();
221
204
propertySources .addFirst (new MockPropertySource ().withProperty ("key" , "value" ));
222
- PropertyResolver resolver = new PropertySourcesPropertyResolver (propertySources );
223
- assertThat (resolver .resolveRequiredPlaceholders ("Replace this ${key}" )).isEqualTo ("Replace this value" );
205
+ assertThat (propertyResolver .resolveRequiredPlaceholders ("Replace this ${key}" )).isEqualTo ("Replace this value" );
224
206
}
225
207
226
208
@ Test
227
209
void resolveRequiredPlaceholders_withUnresolvable () {
228
- MutablePropertySources propertySources = new MutablePropertySources ();
229
210
propertySources .addFirst (new MockPropertySource ().withProperty ("key" , "value" ));
230
- PropertyResolver resolver = new PropertySourcesPropertyResolver (propertySources );
231
- assertThatExceptionOfType (PlaceholderResolutionException .class ).isThrownBy (() ->
232
- resolver .resolveRequiredPlaceholders ("Replace this ${key} plus ${unknown}" ));
211
+ assertThatExceptionOfType (PlaceholderResolutionException .class )
212
+ .isThrownBy (() -> propertyResolver .resolveRequiredPlaceholders ("Replace this ${key} plus ${unknown}" ));
233
213
}
234
214
235
215
@ Test
236
216
void resolveRequiredPlaceholders_withDefaultValue () {
237
- MutablePropertySources propertySources = new MutablePropertySources ();
238
217
propertySources .addFirst (new MockPropertySource ().withProperty ("key" , "value" ));
239
- PropertyResolver resolver = new PropertySourcesPropertyResolver (propertySources );
240
- assertThat (resolver .resolveRequiredPlaceholders ("Replace this ${key} plus ${unknown:defaultValue}" ))
218
+ assertThat (propertyResolver .resolveRequiredPlaceholders ("Replace this ${key} plus ${unknown:defaultValue}" ))
241
219
.isEqualTo ("Replace this value plus defaultValue" );
242
220
}
243
221
244
222
@ Test
245
223
void resolveRequiredPlaceholders_withNullInput () {
246
- assertThatIllegalArgumentException ().isThrownBy (() ->
247
- new PropertySourcesPropertyResolver (new MutablePropertySources ()).resolveRequiredPlaceholders (null ));
224
+ assertThatIllegalArgumentException ().isThrownBy (() -> propertyResolver .resolveRequiredPlaceholders (null ));
248
225
}
249
226
250
227
@ Test
@@ -256,17 +233,17 @@ void setRequiredProperties_andValidateRequiredProperties() {
256
233
propertyResolver .setRequiredProperties ("foo" , "bar" );
257
234
258
235
// neither foo nor bar properties are present -> validating should throw
259
- assertThatExceptionOfType (MissingRequiredPropertiesException .class ). isThrownBy (
260
- propertyResolver ::validateRequiredProperties )
261
- .withMessage ("The following properties were declared as required " +
262
- "but could not be resolved: [foo, bar]" );
236
+ assertThatExceptionOfType (MissingRequiredPropertiesException .class )
237
+ . isThrownBy ( propertyResolver ::validateRequiredProperties )
238
+ .withMessage ("The following properties were declared as required " +
239
+ "but could not be resolved: [foo, bar]" );
263
240
264
241
// add foo property -> validation should fail only on missing 'bar' property
265
242
testProperties .put ("foo" , "fooValue" );
266
- assertThatExceptionOfType (MissingRequiredPropertiesException .class ). isThrownBy (
267
- propertyResolver ::validateRequiredProperties )
268
- .withMessage ("The following properties were declared as required " +
269
- "but could not be resolved: [bar]" );
243
+ assertThatExceptionOfType (MissingRequiredPropertiesException .class )
244
+ . isThrownBy ( propertyResolver ::validateRequiredProperties )
245
+ .withMessage ("The following properties were declared as required " +
246
+ "but could not be resolved: [bar]" );
270
247
271
248
// add bar property -> validation should pass, even with an empty string value
272
249
testProperties .put ("bar" , "" );
@@ -291,13 +268,13 @@ void resolveNestedPropertyPlaceholders() {
291
268
assertThat (pr .getProperty ("p2" )).isEqualTo ("v2" );
292
269
assertThat (pr .getProperty ("p3" )).isEqualTo ("v1:v2" );
293
270
assertThat (pr .getProperty ("p4" )).isEqualTo ("v1:v2" );
294
- assertThatExceptionOfType (PlaceholderResolutionException .class ). isThrownBy (() ->
295
- pr .getProperty ("p5" ))
296
- .withMessageContaining ("Could not resolve placeholder 'bogus' in value \" ${p1}:${p2}:${bogus}\" " );
271
+ assertThatExceptionOfType (PlaceholderResolutionException .class )
272
+ . isThrownBy (() -> pr .getProperty ("p5" ))
273
+ .withMessageContaining ("Could not resolve placeholder 'bogus' in value \" ${p1}:${p2}:${bogus}\" " );
297
274
assertThat (pr .getProperty ("p6" )).isEqualTo ("v1:v2:def" );
298
- assertThatExceptionOfType (PlaceholderResolutionException .class ). isThrownBy (() ->
299
- pr .getProperty ("pL" ))
300
- .withMessageContaining ("Circular" );
275
+ assertThatExceptionOfType (PlaceholderResolutionException .class )
276
+ . isThrownBy (() -> pr .getProperty ("pL" ))
277
+ .withMessageContaining ("Circular" );
301
278
}
302
279
303
280
@ Test
@@ -349,9 +326,9 @@ void ignoreUnresolvableNestedPlaceholdersIsConfigurable() {
349
326
350
327
// placeholders nested within the value of "p4" are unresolvable and cause an
351
328
// exception by default
352
- assertThatExceptionOfType (PlaceholderResolutionException .class ). isThrownBy (() ->
353
- pr .getProperty ("p4" ))
354
- .withMessageContaining ("Could not resolve placeholder 'bogus' in value \" ${p1}:${p2}:${bogus}\" " );
329
+ assertThatExceptionOfType (PlaceholderResolutionException .class )
330
+ . isThrownBy (() -> pr .getProperty ("p4" ))
331
+ .withMessageContaining ("Could not resolve placeholder 'bogus' in value \" ${p1}:${p2}:${bogus}\" " );
355
332
356
333
// relax the treatment of unresolvable nested placeholders
357
334
pr .setIgnoreUnresolvableNestedPlaceholders (true );
@@ -361,9 +338,58 @@ void ignoreUnresolvableNestedPlaceholdersIsConfigurable() {
361
338
// resolve[Nested]Placeholders methods behave as usual regardless the value of
362
339
// ignoreUnresolvableNestedPlaceholders
363
340
assertThat (pr .resolvePlaceholders ("${p1}:${p2}:${bogus}" )).isEqualTo ("v1:v2:${bogus}" );
364
- assertThatExceptionOfType (PlaceholderResolutionException .class ).isThrownBy (() ->
365
- pr .resolveRequiredPlaceholders ("${p1}:${p2}:${bogus}" ))
366
- .withMessageContaining ("Could not resolve placeholder 'bogus' in value \" ${p1}:${p2}:${bogus}\" " );
341
+ assertThatExceptionOfType (PlaceholderResolutionException .class )
342
+ .isThrownBy (() -> pr .resolveRequiredPlaceholders ("${p1}:${p2}:${bogus}" ))
343
+ .withMessageContaining ("Could not resolve placeholder 'bogus' in value \" ${p1}:${p2}:${bogus}\" " );
344
+ }
345
+
346
+
347
+ @ Nested
348
+ class EscapedPlaceholderTests {
349
+
350
+ @ Test // gh-34720
351
+ void escapedPlaceholdersAreNotEvaluated () {
352
+ testProperties .put ("prop1" , "value1" );
353
+ testProperties .put ("prop2" , "value2\\ ${prop1}" );
354
+
355
+ assertThat (propertyResolver .getProperty ("prop2" )).isEqualTo ("value2${prop1}" );
356
+ }
357
+
358
+ @ Test // gh-34720
359
+ void escapedPlaceholdersAreNotEvaluatedWithCharSequenceValues () {
360
+ testProperties .put ("prop1" , "value1" );
361
+ testProperties .put ("prop2" , new StringBuilder ("value2\\ ${prop1}" ));
362
+
363
+ assertThat (propertyResolver .getProperty ("prop2" )).isEqualTo ("value2${prop1}" );
364
+ }
365
+
366
+ @ Test // gh-34720
367
+ void multipleEscapedPlaceholdersArePreserved () {
368
+ testProperties .put ("prop1" , "value1" );
369
+ testProperties .put ("prop2" , "value2" );
370
+ testProperties .put ("complex" , "start\\ ${prop1}middle\\ ${prop2}end" );
371
+
372
+ assertThat (propertyResolver .getProperty ("complex" )).isEqualTo ("start${prop1}middle${prop2}end" );
373
+ }
374
+
375
+ @ Test // gh-34720
376
+ void doubleBackslashesAreProcessedCorrectly () {
377
+ testProperties .put ("prop1" , "value1" );
378
+ testProperties .put ("doubleEscaped" , "value2\\ \\ ${prop1}" );
379
+
380
+ assertThat (propertyResolver .getProperty ("doubleEscaped" )).isEqualTo ("value2\\ ${prop1}" );
381
+ }
382
+
383
+ @ Test // gh-34720
384
+ void escapedPlaceholdersInNestedPropertiesAreNotEvaluated () {
385
+ testProperties .put ("p1" , "v1" );
386
+ testProperties .put ("p2" , "v2" );
387
+ testProperties .put ("escaped" , "prefix-\\ ${p1}" );
388
+ testProperties .put ("nested" , "${escaped}-${p2}" );
389
+
390
+ assertThat (propertyResolver .getProperty ("nested" )).isEqualTo ("prefix-${p1}-v2" );
391
+ }
392
+
367
393
}
368
394
369
395
}
0 commit comments