16
16
17
17
package org .springframework .util ;
18
18
19
- import java .util .Properties ;
19
+ import java .util .Map ;
20
20
import java .util .stream .Stream ;
21
21
22
22
import org .junit .jupiter .api .Nested ;
36
36
import static org .mockito .BDDMockito .given ;
37
37
import static org .mockito .Mockito .inOrder ;
38
38
import static org .mockito .Mockito .mock ;
39
- import static org .mockito .Mockito .verify ;
40
39
import static org .mockito .Mockito .verifyNoMoreInteractions ;
41
40
42
41
/**
43
42
* Tests for {@link PlaceholderParser}.
44
43
*
45
44
* @author Stephane Nicoll
45
+ * @author Sam Brannen
46
46
*/
47
47
class PlaceholderParserTests {
48
48
@@ -54,11 +54,11 @@ class OnlyPlaceholderTests {
54
54
@ ParameterizedTest (name = "{0} -> {1}" )
55
55
@ MethodSource ("placeholders" )
56
56
void placeholderIsReplaced (String text , String expected ) {
57
- Properties properties = new Properties ();
58
- properties . setProperty ( "firstName" , "John" );
59
- properties . setProperty ( "nested0" , "first" );
60
- properties . setProperty ( "nested1" , "Name" );
61
- assertThat (this .parser .replacePlaceholders (text , properties ::getProperty )).isEqualTo (expected );
57
+ Map < String , String > properties = Map . of (
58
+ "firstName" , "John" ,
59
+ "nested0" , "first" ,
60
+ "nested1" , "Name" );
61
+ assertThat (this .parser .replacePlaceholders (text , properties ::get )).isEqualTo (expected );
62
62
}
63
63
64
64
static Stream <Arguments > placeholders () {
@@ -79,13 +79,13 @@ static Stream<Arguments> placeholders() {
79
79
@ ParameterizedTest (name = "{0} -> {1}" )
80
80
@ MethodSource ("nestedPlaceholders" )
81
81
void nestedPlaceholdersAreReplaced (String text , String expected ) {
82
- Properties properties = new Properties ();
83
- properties . setProperty ( "p1" , "v1" );
84
- properties . setProperty ( "p2" , "v2" );
85
- properties . setProperty ( "p3" , "${p1}:${p2}" ); // nested placeholders
86
- properties . setProperty ( "p4" , "${p3}" ); // deeply nested placeholders
87
- properties . setProperty ( "p5" , "${p1}:${p2}:${bogus}" ); // unresolvable placeholder
88
- assertThat (this .parser .replacePlaceholders (text , properties ::getProperty )).isEqualTo (expected );
82
+ Map < String , String > properties = Map . of (
83
+ "p1" , "v1" ,
84
+ "p2" , "v2" ,
85
+ "p3" , "${p1}:${p2}" , // nested placeholders
86
+ "p4" , "${p3}" , // deeply nested placeholders
87
+ "p5" , "${p1}:${p2}:${bogus}" ); // unresolvable placeholder
88
+ assertThat (this .parser .replacePlaceholders (text , properties ::get )).isEqualTo (expected );
89
89
}
90
90
91
91
static Stream <Arguments > nestedPlaceholders () {
@@ -101,69 +101,59 @@ static Stream<Arguments> nestedPlaceholders() {
101
101
@ Test
102
102
void parseWithSinglePlaceholder () {
103
103
PlaceholderResolver resolver = mockPlaceholderResolver ("firstName" , "John" );
104
- assertThat (this .parser .replacePlaceholders ("${firstName}" , resolver ))
105
- .isEqualTo ("John" );
106
- verify (resolver ).resolvePlaceholder ("firstName" );
107
- verifyNoMoreInteractions (resolver );
104
+ assertThat (this .parser .replacePlaceholders ("${firstName}" , resolver )).isEqualTo ("John" );
105
+ verifyPlaceholderResolutions (resolver , "firstName" );
108
106
}
109
107
110
108
@ Test
111
109
void parseWithPlaceholderAndPrefixText () {
112
110
PlaceholderResolver resolver = mockPlaceholderResolver ("firstName" , "John" );
113
- assertThat (this .parser .replacePlaceholders ("This is ${firstName}" , resolver ))
114
- .isEqualTo ("This is John" );
115
- verify (resolver ).resolvePlaceholder ("firstName" );
116
- verifyNoMoreInteractions (resolver );
111
+ assertThat (this .parser .replacePlaceholders ("This is ${firstName}" , resolver )).isEqualTo ("This is John" );
112
+ verifyPlaceholderResolutions (resolver , "firstName" );
117
113
}
118
114
119
115
@ Test
120
116
void parseWithMultiplePlaceholdersAndText () {
121
117
PlaceholderResolver resolver = mockPlaceholderResolver ("firstName" , "John" , "lastName" , "Smith" );
122
118
assertThat (this .parser .replacePlaceholders ("User: ${firstName} - ${lastName}." , resolver ))
123
119
.isEqualTo ("User: John - Smith." );
124
- verify (resolver ).resolvePlaceholder ("firstName" );
125
- verify (resolver ).resolvePlaceholder ("lastName" );
126
- verifyNoMoreInteractions (resolver );
120
+ verifyPlaceholderResolutions (resolver , "firstName" , "lastName" );
127
121
}
128
122
129
123
@ Test
130
124
void parseWithNestedPlaceholderInKey () {
131
- PlaceholderResolver resolver = mockPlaceholderResolver (
132
- "nested" , "Name" , "firstName" , "John" );
133
- assertThat (this .parser .replacePlaceholders ("${first${nested}}" , resolver ))
134
- .isEqualTo ("John" );
125
+ PlaceholderResolver resolver = mockPlaceholderResolver ("nested" , "Name" , "firstName" , "John" );
126
+ assertThat (this .parser .replacePlaceholders ("${first${nested}}" , resolver )).isEqualTo ("John" );
135
127
verifyPlaceholderResolutions (resolver , "nested" , "firstName" );
136
128
}
137
129
138
130
@ Test
139
131
void parseWithMultipleNestedPlaceholdersInKey () {
140
- PlaceholderResolver resolver = mockPlaceholderResolver (
141
- "nested0" , "first" , "nested1" , "Name" , "firstName" , "John" );
142
- assertThat (this .parser .replacePlaceholders ("${${nested0}${nested1}}" , resolver ))
143
- .isEqualTo ("John" );
132
+ PlaceholderResolver resolver = mockPlaceholderResolver ("nested0" , "first" , "nested1" , "Name" , "firstName" , "John" );
133
+ assertThat (this .parser .replacePlaceholders ("${${nested0}${nested1}}" , resolver )).isEqualTo ("John" );
144
134
verifyPlaceholderResolutions (resolver , "nested0" , "nested1" , "firstName" );
145
135
}
146
136
147
137
@ Test
148
- void placeholdersWithSeparatorAreHandledAsIs () {
138
+ void placeholderValueContainingSeparatorIsHandledAsIs () {
149
139
PlaceholderResolver resolver = mockPlaceholderResolver ("my:test" , "value" );
150
140
assertThat (this .parser .replacePlaceholders ("${my:test}" , resolver )).isEqualTo ("value" );
151
141
verifyPlaceholderResolutions (resolver , "my:test" );
152
142
}
153
143
154
144
@ Test
155
145
void placeholdersWithoutEscapeCharAreNotEscaped () {
156
- PlaceholderResolver resolver = mockPlaceholderResolver ("test " , "value " );
157
- assertThat (this .parser .replacePlaceholders ("\\ ${test }" , resolver )).isEqualTo ("\\ value " );
158
- verifyPlaceholderResolutions (resolver , "test " );
146
+ PlaceholderResolver resolver = mockPlaceholderResolver ("p1 " , "v1 " );
147
+ assertThat (this .parser .replacePlaceholders ("\\ ${p1 }" , resolver )).isEqualTo ("\\ v1 " );
148
+ verifyPlaceholderResolutions (resolver , "p1 " );
159
149
}
160
150
161
151
@ Test
162
- void textWithInvalidPlaceholderIsMerged () {
152
+ void textWithInvalidPlaceholderSyntaxIsMerged () {
163
153
String text = "test${of${with${and${" ;
164
154
ParsedValue parsedValue = this .parser .parse (text );
165
- assertThat (parsedValue .parts ()).singleElement ().isInstanceOfSatisfying (
166
- TextPart . class , textPart -> assertThat (textPart .text ()).isEqualTo (text ));
155
+ assertThat (parsedValue .parts ()).singleElement ().isInstanceOfSatisfying (TextPart . class ,
156
+ textPart -> assertThat (textPart .text ()).isEqualTo (text ));
167
157
}
168
158
169
159
}
@@ -176,11 +166,11 @@ class DefaultValueTests {
176
166
@ ParameterizedTest (name = "{0} -> {1}" )
177
167
@ MethodSource ("placeholders" )
178
168
void placeholderIsReplaced (String text , String expected ) {
179
- Properties properties = new Properties ();
180
- properties . setProperty ( "firstName" , "John" );
181
- properties . setProperty ( "nested0" , "first" );
182
- properties . setProperty ( "nested1" , "Name" );
183
- assertThat (this .parser .replacePlaceholders (text , properties ::getProperty )).isEqualTo (expected );
169
+ Map < String , String > properties = Map . of (
170
+ "firstName" , "John" ,
171
+ "nested0" , "first" ,
172
+ "nested1" , "Name" );
173
+ assertThat (this .parser .replacePlaceholders (text , properties ::get )).isEqualTo (expected );
184
174
}
185
175
186
176
static Stream <Arguments > placeholders () {
@@ -199,14 +189,14 @@ static Stream<Arguments> placeholders() {
199
189
@ ParameterizedTest (name = "{0} -> {1}" )
200
190
@ MethodSource ("nestedPlaceholders" )
201
191
void nestedPlaceholdersAreReplaced (String text , String expected ) {
202
- Properties properties = new Properties ();
203
- properties . setProperty ( "p1" , "v1" );
204
- properties . setProperty ( "p2" , "v2" );
205
- properties . setProperty ( "p3" , "${p1}:${p2}" ); // nested placeholders
206
- properties . setProperty ( "p4" , "${p3}" ); // deeply nested placeholders
207
- properties . setProperty ( "p5" , "${p1}:${p2}:${bogus}" ); // unresolvable placeholder
208
- properties . setProperty ( "p6" , "${p1}:${p2}:${bogus:def}" ); // unresolvable w/ default
209
- assertThat (this .parser .replacePlaceholders (text , properties ::getProperty )).isEqualTo (expected );
192
+ Map < String , String > properties = Map . of (
193
+ "p1" , "v1" ,
194
+ "p2" , "v2" ,
195
+ "p3" , "${p1}:${p2}" , // nested placeholders
196
+ "p4" , "${p3}" , // deeply nested placeholders
197
+ "p5" , "${p1}:${p2}:${bogus}" , // unresolvable placeholder
198
+ "p6" , "${p1}:${p2}:${bogus:def}" ); // unresolvable w/ default
199
+ assertThat (this .parser .replacePlaceholders (text , properties ::get )).isEqualTo (expected );
210
200
}
211
201
212
202
static Stream <Arguments > nestedPlaceholders () {
@@ -225,11 +215,11 @@ static Stream<Arguments> nestedPlaceholders() {
225
215
@ ParameterizedTest (name = "{0} -> {1}" )
226
216
@ MethodSource ("exactMatchPlaceholders" )
227
217
void placeholdersWithExactMatchAreConsidered (String text , String expected ) {
228
- Properties properties = new Properties ();
229
- properties . setProperty ( "prefix://my-service" , "example-service" );
230
- properties . setProperty ( "px" , "prefix" );
231
- properties . setProperty ( "p1" , "${prefix://my-service}" );
232
- assertThat (this .parser .replacePlaceholders (text , properties ::getProperty )).isEqualTo (expected );
218
+ Map < String , String > properties = Map . of (
219
+ "prefix://my-service" , "example-service" ,
220
+ "px" , "prefix" ,
221
+ "p1" , "${prefix://my-service}" );
222
+ assertThat (this .parser .replacePlaceholders (text , properties ::get )).isEqualTo (expected );
233
223
}
234
224
235
225
static Stream <Arguments > exactMatchPlaceholders () {
@@ -242,74 +232,55 @@ static Stream<Arguments> exactMatchPlaceholders() {
242
232
@ Test
243
233
void parseWithKeyEqualsToText () {
244
234
PlaceholderResolver resolver = mockPlaceholderResolver ("firstName" , "Steve" );
245
- assertThat (this .parser .replacePlaceholders ("${firstName}" , resolver ))
246
- .isEqualTo ("Steve" );
235
+ assertThat (this .parser .replacePlaceholders ("${firstName}" , resolver )).isEqualTo ("Steve" );
247
236
verifyPlaceholderResolutions (resolver , "firstName" );
248
237
}
249
238
250
239
@ Test
251
240
void parseWithHardcodedFallback () {
252
241
PlaceholderResolver resolver = mockPlaceholderResolver ();
253
- assertThat (this .parser .replacePlaceholders ("${firstName:Steve}" , resolver ))
254
- .isEqualTo ("Steve" );
242
+ assertThat (this .parser .replacePlaceholders ("${firstName:Steve}" , resolver )).isEqualTo ("Steve" );
255
243
verifyPlaceholderResolutions (resolver , "firstName:Steve" , "firstName" );
256
244
}
257
245
258
246
@ Test
259
247
void parseWithNestedPlaceholderInKeyUsingFallback () {
260
248
PlaceholderResolver resolver = mockPlaceholderResolver ("firstName" , "John" );
261
- assertThat (this .parser .replacePlaceholders ("${first${invalid:Name}}" , resolver ))
262
- .isEqualTo ("John" );
249
+ assertThat (this .parser .replacePlaceholders ("${first${invalid:Name}}" , resolver )).isEqualTo ("John" );
263
250
verifyPlaceholderResolutions (resolver , "invalid:Name" , "invalid" , "firstName" );
264
251
}
265
252
266
253
@ Test
267
254
void parseWithFallbackUsingPlaceholder () {
268
255
PlaceholderResolver resolver = mockPlaceholderResolver ("firstName" , "John" );
269
- assertThat (this .parser .replacePlaceholders ("${invalid:${firstName}}" , resolver ))
270
- .isEqualTo ("John" );
256
+ assertThat (this .parser .replacePlaceholders ("${invalid:${firstName}}" , resolver )).isEqualTo ("John" );
271
257
verifyPlaceholderResolutions (resolver , "invalid" , "firstName" );
272
258
}
273
259
274
260
}
275
261
276
- @ Nested // Tests with the use of the escape character
262
+ /**
263
+ * Tests that use the escape character.
264
+ */
265
+ @ Nested
277
266
class EscapedTests {
278
267
279
268
private final PlaceholderParser parser = new PlaceholderParser ("${" , "}" , ":" , '\\' , true );
280
269
281
- @ ParameterizedTest (name = "{0} -> {1}" )
282
- @ MethodSource ("escapedInNestedPlaceholders" )
283
- void escapedSeparatorInNestedPlaceholder (String text , String expected ) {
284
- Properties properties = new Properties ();
285
- properties .setProperty ("app.environment" , "qa" );
286
- properties .setProperty ("app.service" , "protocol" );
287
- properties .setProperty ("protocol://host/qa/name" , "protocol://example.com/qa/name" );
288
- properties .setProperty ("service/host/qa/name" , "https://example.com/qa/name" );
289
- properties .setProperty ("service/host/qa/name:value" , "https://example.com/qa/name-value" );
290
- assertThat (this .parser .replacePlaceholders (text , properties ::getProperty )).isEqualTo (expected );
291
- }
292
-
293
- static Stream <Arguments > escapedInNestedPlaceholders () {
294
- return Stream .of (
295
- Arguments .of ("${protocol\\ ://host/${app.environment}/name}" , "protocol://example.com/qa/name" ),
296
- Arguments .of ("${${app.service}\\ ://host/${app.environment}/name}" , "protocol://example.com/qa/name" ),
297
- Arguments .of ("${service/host/${app.environment}/name:\\ value}" , "https://example.com/qa/name" ),
298
- Arguments .of ("${service/host/${name\\ :value}/}" , "${service/host/${name:value}/}" ));
299
- }
300
-
301
270
@ ParameterizedTest (name = "{0} -> {1}" )
302
271
@ MethodSource ("escapedPlaceholders" )
303
272
void escapedPlaceholderIsNotReplaced (String text , String expected ) {
304
- PlaceholderResolver resolver = mockPlaceholderResolver (
305
- "firstName" , "John" , "nested0" , "first" , "nested1" , "Name" ,
273
+ Map < String , String > properties = Map . of (
274
+ "firstName" , "John" ,
306
275
"${test}" , "John" ,
307
- "p1" , "v1" , "p2" , "\\ ${p1:default}" , "p3" , "${p2}" ,
276
+ "p1" , "v1" ,
277
+ "p2" , "\\ ${p1:default}" ,
278
+ "p3" , "${p2}" ,
308
279
"p4" , "adc${p0:\\ ${p1}}" ,
309
280
"p5" , "adc${\\ ${p0}:${p1}}" ,
310
281
"p6" , "adc${p0:def\\ ${p1}}" ,
311
282
"p7" , "adc\\ ${" );
312
- assertThat (this .parser .replacePlaceholders (text , resolver )).isEqualTo (expected );
283
+ assertThat (this .parser .replacePlaceholders (text , properties :: get )).isEqualTo (expected );
313
284
}
314
285
315
286
static Stream <Arguments > escapedPlaceholders () {
@@ -324,18 +295,15 @@ static Stream<Arguments> escapedPlaceholders() {
324
295
Arguments .of ("${p4}" , "adc${p1}" ),
325
296
Arguments .of ("${p5}" , "adcv1" ),
326
297
Arguments .of ("${p6}" , "adcdef${p1}" ),
327
- Arguments .of ("${p7}" , "adc\\ ${" ));
328
-
298
+ Arguments .of ("${p7}" , "adc\\ ${" )
299
+ );
329
300
}
330
301
331
302
@ ParameterizedTest (name = "{0} -> {1}" )
332
303
@ MethodSource ("escapedSeparators" )
333
304
void escapedSeparatorIsNotReplaced (String text , String expected ) {
334
- Properties properties = new Properties ();
335
- properties .setProperty ("first:Name" , "John" );
336
- properties .setProperty ("nested0" , "first" );
337
- properties .setProperty ("nested1" , "Name" );
338
- assertThat (this .parser .replacePlaceholders (text , properties ::getProperty )).isEqualTo (expected );
305
+ Map <String , String > properties = Map .of ("first:Name" , "John" );
306
+ assertThat (this .parser .replacePlaceholders (text , properties ::get )).isEqualTo (expected );
339
307
}
340
308
341
309
static Stream <Arguments > escapedSeparators () {
@@ -345,6 +313,26 @@ static Stream<Arguments> escapedSeparators() {
345
313
);
346
314
}
347
315
316
+ @ ParameterizedTest (name = "{0} -> {1}" )
317
+ @ MethodSource ("escapedSeparatorsInNestedPlaceholders" )
318
+ void escapedSeparatorInNestedPlaceholderIsNotReplaced (String text , String expected ) {
319
+ Map <String , String > properties = Map .of (
320
+ "app.environment" , "qa" ,
321
+ "app.service" , "protocol" ,
322
+ "protocol://host/qa/name" , "protocol://example.com/qa/name" ,
323
+ "service/host/qa/name" , "https://example.com/qa/name" ,
324
+ "service/host/qa/name:value" , "https://example.com/qa/name-value" );
325
+ assertThat (this .parser .replacePlaceholders (text , properties ::get )).isEqualTo (expected );
326
+ }
327
+
328
+ static Stream <Arguments > escapedSeparatorsInNestedPlaceholders () {
329
+ return Stream .of (
330
+ Arguments .of ("${protocol\\ ://host/${app.environment}/name}" , "protocol://example.com/qa/name" ),
331
+ Arguments .of ("${${app.service}\\ ://host/${app.environment}/name}" , "protocol://example.com/qa/name" ),
332
+ Arguments .of ("${service/host/${app.environment}/name:\\ value}" , "https://example.com/qa/name" ),
333
+ Arguments .of ("${service/host/${name\\ :value}/}" , "${service/host/${name:value}/}" ));
334
+ }
335
+
348
336
}
349
337
350
338
@ Nested
@@ -354,34 +342,38 @@ class ExceptionTests {
354
342
355
343
@ Test
356
344
void textWithCircularReference () {
357
- PlaceholderResolver resolver = mockPlaceholderResolver ("pL" , "${pR}" , "pR" , "${pL}" );
358
- assertThatThrownBy (() -> this .parser .replacePlaceholders ("${pL}" , resolver ))
345
+ Map <String , String > properties = Map .of (
346
+ "pL" , "${pR}" ,
347
+ "pR" , "${pL}" );
348
+ assertThatThrownBy (() -> this .parser .replacePlaceholders ("${pL}" , properties ::get ))
359
349
.isInstanceOf (PlaceholderResolutionException .class )
360
350
.hasMessage ("Circular placeholder reference 'pL' in value \" ${pL}\" <-- \" ${pR}\" <-- \" ${pL}\" " );
361
351
}
362
352
363
353
@ Test
364
354
void unresolvablePlaceholderIsReported () {
365
- PlaceholderResolver resolver = mockPlaceholderResolver ();
366
355
assertThatExceptionOfType (PlaceholderResolutionException .class )
367
- .isThrownBy (() -> this .parser .replacePlaceholders ("${bogus}" , resolver ))
368
- .withMessage ("Could not resolve placeholder 'bogus' in value \" ${bogus}\" " )
356
+ .isThrownBy (() -> this .parser .replacePlaceholders ("X ${bogus}Z " , placeholderName -> null ))
357
+ .withMessage ("Could not resolve placeholder 'bogus' in value \" X ${bogus}Z \" " )
369
358
.withNoCause ();
370
359
}
371
360
372
361
@ Test
373
362
void unresolvablePlaceholderInNestedPlaceholderIsReportedWithChain () {
374
- PlaceholderResolver resolver = mockPlaceholderResolver ("p1" , "v1" , "p2" , "v2" ,
363
+ Map <String , String > properties = Map .of (
364
+ "p1" , "v1" ,
365
+ "p2" , "v2" ,
375
366
"p3" , "${p1}:${p2}:${bogus}" );
376
367
assertThatExceptionOfType (PlaceholderResolutionException .class )
377
- .isThrownBy (() -> this .parser .replacePlaceholders ("${p3}" , resolver ))
368
+ .isThrownBy (() -> this .parser .replacePlaceholders ("${p3}" , properties :: get ))
378
369
.withMessage ("Could not resolve placeholder 'bogus' in value \" ${p1}:${p2}:${bogus}\" <-- \" ${p3}\" " )
379
370
.withNoCause ();
380
371
}
381
372
382
373
}
383
374
384
- PlaceholderResolver mockPlaceholderResolver (String ... pairs ) {
375
+
376
+ private static PlaceholderResolver mockPlaceholderResolver (String ... pairs ) {
385
377
if (pairs .length % 2 == 1 ) {
386
378
throw new IllegalArgumentException ("size must be even, it is a set of key=value pairs" );
387
379
}
@@ -394,7 +386,7 @@ PlaceholderResolver mockPlaceholderResolver(String... pairs) {
394
386
return resolver ;
395
387
}
396
388
397
- void verifyPlaceholderResolutions (PlaceholderResolver mock , String ... placeholders ) {
389
+ private static void verifyPlaceholderResolutions (PlaceholderResolver mock , String ... placeholders ) {
398
390
InOrder ordered = inOrder (mock );
399
391
for (String placeholder : placeholders ) {
400
392
ordered .verify (mock ).resolvePlaceholder (placeholder );
0 commit comments