Browse Source

Merge branch '6.2.x'

pull/34876/head
Sam Brannen 9 months ago
parent
commit
3fb9c2b507
  1. 215
      spring-core/src/test/java/org/springframework/util/PlaceholderParserTests.java

215
spring-core/src/test/java/org/springframework/util/PlaceholderParserTests.java

@ -16,7 +16,7 @@
package org.springframework.util; package org.springframework.util;
import java.util.Properties; import java.util.Map;
import java.util.stream.Stream; import java.util.stream.Stream;
import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Nested;
@ -36,13 +36,13 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.mockito.BDDMockito.given; import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.verifyNoMoreInteractions;
/** /**
* Tests for {@link PlaceholderParser}. * Tests for {@link PlaceholderParser}.
* *
* @author Stephane Nicoll * @author Stephane Nicoll
* @author Sam Brannen
*/ */
class PlaceholderParserTests { class PlaceholderParserTests {
@ -54,11 +54,11 @@ class PlaceholderParserTests {
@ParameterizedTest(name = "{0} -> {1}") @ParameterizedTest(name = "{0} -> {1}")
@MethodSource("placeholders") @MethodSource("placeholders")
void placeholderIsReplaced(String text, String expected) { void placeholderIsReplaced(String text, String expected) {
Properties properties = new Properties(); Map<String, String> properties = Map.of(
properties.setProperty("firstName", "John"); "firstName", "John",
properties.setProperty("nested0", "first"); "nested0", "first",
properties.setProperty("nested1", "Name"); "nested1", "Name");
assertThat(this.parser.replacePlaceholders(text, properties::getProperty)).isEqualTo(expected); assertThat(this.parser.replacePlaceholders(text, properties::get)).isEqualTo(expected);
} }
static Stream<Arguments> placeholders() { static Stream<Arguments> placeholders() {
@ -79,13 +79,13 @@ class PlaceholderParserTests {
@ParameterizedTest(name = "{0} -> {1}") @ParameterizedTest(name = "{0} -> {1}")
@MethodSource("nestedPlaceholders") @MethodSource("nestedPlaceholders")
void nestedPlaceholdersAreReplaced(String text, String expected) { void nestedPlaceholdersAreReplaced(String text, String expected) {
Properties properties = new Properties(); Map<String, String> properties = Map.of(
properties.setProperty("p1", "v1"); "p1", "v1",
properties.setProperty("p2", "v2"); "p2", "v2",
properties.setProperty("p3", "${p1}:${p2}"); // nested placeholders "p3", "${p1}:${p2}", // nested placeholders
properties.setProperty("p4", "${p3}"); // deeply nested placeholders "p4", "${p3}", // deeply nested placeholders
properties.setProperty("p5", "${p1}:${p2}:${bogus}"); // unresolvable placeholder "p5", "${p1}:${p2}:${bogus}"); // unresolvable placeholder
assertThat(this.parser.replacePlaceholders(text, properties::getProperty)).isEqualTo(expected); assertThat(this.parser.replacePlaceholders(text, properties::get)).isEqualTo(expected);
} }
static Stream<Arguments> nestedPlaceholders() { static Stream<Arguments> nestedPlaceholders() {
@ -101,19 +101,15 @@ class PlaceholderParserTests {
@Test @Test
void parseWithSinglePlaceholder() { void parseWithSinglePlaceholder() {
PlaceholderResolver resolver = mockPlaceholderResolver("firstName", "John"); PlaceholderResolver resolver = mockPlaceholderResolver("firstName", "John");
assertThat(this.parser.replacePlaceholders("${firstName}", resolver)) assertThat(this.parser.replacePlaceholders("${firstName}", resolver)).isEqualTo("John");
.isEqualTo("John"); verifyPlaceholderResolutions(resolver, "firstName");
verify(resolver).resolvePlaceholder("firstName");
verifyNoMoreInteractions(resolver);
} }
@Test @Test
void parseWithPlaceholderAndPrefixText() { void parseWithPlaceholderAndPrefixText() {
PlaceholderResolver resolver = mockPlaceholderResolver("firstName", "John"); PlaceholderResolver resolver = mockPlaceholderResolver("firstName", "John");
assertThat(this.parser.replacePlaceholders("This is ${firstName}", resolver)) assertThat(this.parser.replacePlaceholders("This is ${firstName}", resolver)).isEqualTo("This is John");
.isEqualTo("This is John"); verifyPlaceholderResolutions(resolver, "firstName");
verify(resolver).resolvePlaceholder("firstName");
verifyNoMoreInteractions(resolver);
} }
@Test @Test
@ -121,31 +117,25 @@ class PlaceholderParserTests {
PlaceholderResolver resolver = mockPlaceholderResolver("firstName", "John", "lastName", "Smith"); PlaceholderResolver resolver = mockPlaceholderResolver("firstName", "John", "lastName", "Smith");
assertThat(this.parser.replacePlaceholders("User: ${firstName} - ${lastName}.", resolver)) assertThat(this.parser.replacePlaceholders("User: ${firstName} - ${lastName}.", resolver))
.isEqualTo("User: John - Smith."); .isEqualTo("User: John - Smith.");
verify(resolver).resolvePlaceholder("firstName"); verifyPlaceholderResolutions(resolver, "firstName", "lastName");
verify(resolver).resolvePlaceholder("lastName");
verifyNoMoreInteractions(resolver);
} }
@Test @Test
void parseWithNestedPlaceholderInKey() { void parseWithNestedPlaceholderInKey() {
PlaceholderResolver resolver = mockPlaceholderResolver( PlaceholderResolver resolver = mockPlaceholderResolver("nested", "Name", "firstName", "John");
"nested", "Name", "firstName", "John"); assertThat(this.parser.replacePlaceholders("${first${nested}}", resolver)).isEqualTo("John");
assertThat(this.parser.replacePlaceholders("${first${nested}}", resolver))
.isEqualTo("John");
verifyPlaceholderResolutions(resolver, "nested", "firstName"); verifyPlaceholderResolutions(resolver, "nested", "firstName");
} }
@Test @Test
void parseWithMultipleNestedPlaceholdersInKey() { void parseWithMultipleNestedPlaceholdersInKey() {
PlaceholderResolver resolver = mockPlaceholderResolver( PlaceholderResolver resolver = mockPlaceholderResolver("nested0", "first", "nested1", "Name", "firstName", "John");
"nested0", "first", "nested1", "Name", "firstName", "John"); assertThat(this.parser.replacePlaceholders("${${nested0}${nested1}}", resolver)).isEqualTo("John");
assertThat(this.parser.replacePlaceholders("${${nested0}${nested1}}", resolver))
.isEqualTo("John");
verifyPlaceholderResolutions(resolver, "nested0", "nested1", "firstName"); verifyPlaceholderResolutions(resolver, "nested0", "nested1", "firstName");
} }
@Test @Test
void placeholdersWithSeparatorAreHandledAsIs() { void placeholderValueContainingSeparatorIsHandledAsIs() {
PlaceholderResolver resolver = mockPlaceholderResolver("my:test", "value"); PlaceholderResolver resolver = mockPlaceholderResolver("my:test", "value");
assertThat(this.parser.replacePlaceholders("${my:test}", resolver)).isEqualTo("value"); assertThat(this.parser.replacePlaceholders("${my:test}", resolver)).isEqualTo("value");
verifyPlaceholderResolutions(resolver, "my:test"); verifyPlaceholderResolutions(resolver, "my:test");
@ -153,17 +143,20 @@ class PlaceholderParserTests {
@Test @Test
void placeholdersWithoutEscapeCharAreNotEscaped() { void placeholdersWithoutEscapeCharAreNotEscaped() {
PlaceholderResolver resolver = mockPlaceholderResolver("test", "value"); PlaceholderResolver resolver = mockPlaceholderResolver("p1", "v1", "p2", "v2", "p3", "v3", "p4", "v4");
assertThat(this.parser.replacePlaceholders("\\${test}", resolver)).isEqualTo("\\value"); assertThat(this.parser.replacePlaceholders("\\${p1}", resolver)).isEqualTo("\\v1");
verifyPlaceholderResolutions(resolver, "test"); assertThat(this.parser.replacePlaceholders("\\\\${p2}", resolver)).isEqualTo("\\\\v2");
assertThat(this.parser.replacePlaceholders("\\${p3}\\", resolver)).isEqualTo("\\v3\\");
assertThat(this.parser.replacePlaceholders("a\\${p4}\\z", resolver)).isEqualTo("a\\v4\\z");
verifyPlaceholderResolutions(resolver, "p1", "p2", "p3", "p4");
} }
@Test @Test
void textWithInvalidPlaceholderIsMerged() { void textWithInvalidPlaceholderSyntaxIsMerged() {
String text = "test${of${with${and${"; String text = "test${of${with${and${";
ParsedValue parsedValue = this.parser.parse(text); ParsedValue parsedValue = this.parser.parse(text);
assertThat(parsedValue.parts()).singleElement().isInstanceOfSatisfying( assertThat(parsedValue.parts()).singleElement().isInstanceOfSatisfying(TextPart.class,
TextPart.class, textPart -> assertThat(textPart.text()).isEqualTo(text)); textPart -> assertThat(textPart.text()).isEqualTo(text));
} }
} }
@ -176,11 +169,11 @@ class PlaceholderParserTests {
@ParameterizedTest(name = "{0} -> {1}") @ParameterizedTest(name = "{0} -> {1}")
@MethodSource("placeholders") @MethodSource("placeholders")
void placeholderIsReplaced(String text, String expected) { void placeholderIsReplaced(String text, String expected) {
Properties properties = new Properties(); Map<String, String> properties = Map.of(
properties.setProperty("firstName", "John"); "firstName", "John",
properties.setProperty("nested0", "first"); "nested0", "first",
properties.setProperty("nested1", "Name"); "nested1", "Name");
assertThat(this.parser.replacePlaceholders(text, properties::getProperty)).isEqualTo(expected); assertThat(this.parser.replacePlaceholders(text, properties::get)).isEqualTo(expected);
} }
static Stream<Arguments> placeholders() { static Stream<Arguments> placeholders() {
@ -199,14 +192,14 @@ class PlaceholderParserTests {
@ParameterizedTest(name = "{0} -> {1}") @ParameterizedTest(name = "{0} -> {1}")
@MethodSource("nestedPlaceholders") @MethodSource("nestedPlaceholders")
void nestedPlaceholdersAreReplaced(String text, String expected) { void nestedPlaceholdersAreReplaced(String text, String expected) {
Properties properties = new Properties(); Map<String, String> properties = Map.of(
properties.setProperty("p1", "v1"); "p1", "v1",
properties.setProperty("p2", "v2"); "p2", "v2",
properties.setProperty("p3", "${p1}:${p2}"); // nested placeholders "p3", "${p1}:${p2}", // nested placeholders
properties.setProperty("p4", "${p3}"); // deeply nested placeholders "p4", "${p3}", // deeply nested placeholders
properties.setProperty("p5", "${p1}:${p2}:${bogus}"); // unresolvable placeholder "p5", "${p1}:${p2}:${bogus}", // unresolvable placeholder
properties.setProperty("p6", "${p1}:${p2}:${bogus:def}"); // unresolvable w/ default "p6", "${p1}:${p2}:${bogus:def}"); // unresolvable w/ default
assertThat(this.parser.replacePlaceholders(text, properties::getProperty)).isEqualTo(expected); assertThat(this.parser.replacePlaceholders(text, properties::get)).isEqualTo(expected);
} }
static Stream<Arguments> nestedPlaceholders() { static Stream<Arguments> nestedPlaceholders() {
@ -225,11 +218,11 @@ class PlaceholderParserTests {
@ParameterizedTest(name = "{0} -> {1}") @ParameterizedTest(name = "{0} -> {1}")
@MethodSource("exactMatchPlaceholders") @MethodSource("exactMatchPlaceholders")
void placeholdersWithExactMatchAreConsidered(String text, String expected) { void placeholdersWithExactMatchAreConsidered(String text, String expected) {
Properties properties = new Properties(); Map<String, String> properties = Map.of(
properties.setProperty("prefix://my-service", "example-service"); "prefix://my-service", "example-service",
properties.setProperty("px", "prefix"); "px", "prefix",
properties.setProperty("p1", "${prefix://my-service}"); "p1", "${prefix://my-service}");
assertThat(this.parser.replacePlaceholders(text, properties::getProperty)).isEqualTo(expected); assertThat(this.parser.replacePlaceholders(text, properties::get)).isEqualTo(expected);
} }
static Stream<Arguments> exactMatchPlaceholders() { static Stream<Arguments> exactMatchPlaceholders() {
@ -242,74 +235,55 @@ class PlaceholderParserTests {
@Test @Test
void parseWithKeyEqualsToText() { void parseWithKeyEqualsToText() {
PlaceholderResolver resolver = mockPlaceholderResolver("firstName", "Steve"); PlaceholderResolver resolver = mockPlaceholderResolver("firstName", "Steve");
assertThat(this.parser.replacePlaceholders("${firstName}", resolver)) assertThat(this.parser.replacePlaceholders("${firstName}", resolver)).isEqualTo("Steve");
.isEqualTo("Steve");
verifyPlaceholderResolutions(resolver, "firstName"); verifyPlaceholderResolutions(resolver, "firstName");
} }
@Test @Test
void parseWithHardcodedFallback() { void parseWithHardcodedFallback() {
PlaceholderResolver resolver = mockPlaceholderResolver(); PlaceholderResolver resolver = mockPlaceholderResolver();
assertThat(this.parser.replacePlaceholders("${firstName:Steve}", resolver)) assertThat(this.parser.replacePlaceholders("${firstName:Steve}", resolver)).isEqualTo("Steve");
.isEqualTo("Steve");
verifyPlaceholderResolutions(resolver, "firstName:Steve", "firstName"); verifyPlaceholderResolutions(resolver, "firstName:Steve", "firstName");
} }
@Test @Test
void parseWithNestedPlaceholderInKeyUsingFallback() { void parseWithNestedPlaceholderInKeyUsingFallback() {
PlaceholderResolver resolver = mockPlaceholderResolver("firstName", "John"); PlaceholderResolver resolver = mockPlaceholderResolver("firstName", "John");
assertThat(this.parser.replacePlaceholders("${first${invalid:Name}}", resolver)) assertThat(this.parser.replacePlaceholders("${first${invalid:Name}}", resolver)).isEqualTo("John");
.isEqualTo("John");
verifyPlaceholderResolutions(resolver, "invalid:Name", "invalid", "firstName"); verifyPlaceholderResolutions(resolver, "invalid:Name", "invalid", "firstName");
} }
@Test @Test
void parseWithFallbackUsingPlaceholder() { void parseWithFallbackUsingPlaceholder() {
PlaceholderResolver resolver = mockPlaceholderResolver("firstName", "John"); PlaceholderResolver resolver = mockPlaceholderResolver("firstName", "John");
assertThat(this.parser.replacePlaceholders("${invalid:${firstName}}", resolver)) assertThat(this.parser.replacePlaceholders("${invalid:${firstName}}", resolver)).isEqualTo("John");
.isEqualTo("John");
verifyPlaceholderResolutions(resolver, "invalid", "firstName"); verifyPlaceholderResolutions(resolver, "invalid", "firstName");
} }
} }
@Nested // Tests with the use of the escape character /**
* Tests that use the escape character.
*/
@Nested
class EscapedTests { class EscapedTests {
private final PlaceholderParser parser = new PlaceholderParser("${", "}", ":", '\\', true); private final PlaceholderParser parser = new PlaceholderParser("${", "}", ":", '\\', true);
@ParameterizedTest(name = "{0} -> {1}")
@MethodSource("escapedInNestedPlaceholders")
void escapedSeparatorInNestedPlaceholder(String text, String expected) {
Properties properties = new Properties();
properties.setProperty("app.environment", "qa");
properties.setProperty("app.service", "protocol");
properties.setProperty("protocol://host/qa/name", "protocol://example.com/qa/name");
properties.setProperty("service/host/qa/name", "https://example.com/qa/name");
properties.setProperty("service/host/qa/name:value", "https://example.com/qa/name-value");
assertThat(this.parser.replacePlaceholders(text, properties::getProperty)).isEqualTo(expected);
}
static Stream<Arguments> escapedInNestedPlaceholders() {
return Stream.of(
Arguments.of("${protocol\\://host/${app.environment}/name}", "protocol://example.com/qa/name"),
Arguments.of("${${app.service}\\://host/${app.environment}/name}", "protocol://example.com/qa/name"),
Arguments.of("${service/host/${app.environment}/name:\\value}", "https://example.com/qa/name"),
Arguments.of("${service/host/${name\\:value}/}", "${service/host/${name:value}/}"));
}
@ParameterizedTest(name = "{0} -> {1}") @ParameterizedTest(name = "{0} -> {1}")
@MethodSource("escapedPlaceholders") @MethodSource("escapedPlaceholders")
void escapedPlaceholderIsNotReplaced(String text, String expected) { void escapedPlaceholderIsNotReplaced(String text, String expected) {
PlaceholderResolver resolver = mockPlaceholderResolver( Map<String, String> properties = Map.of(
"firstName", "John", "nested0", "first", "nested1", "Name", "firstName", "John",
"${test}", "John", "${test}", "John",
"p1", "v1", "p2", "\\${p1:default}", "p3", "${p2}", "p1", "v1",
"p2", "\\${p1:default}",
"p3", "${p2}",
"p4", "adc${p0:\\${p1}}", "p4", "adc${p0:\\${p1}}",
"p5", "adc${\\${p0}:${p1}}", "p5", "adc${\\${p0}:${p1}}",
"p6", "adc${p0:def\\${p1}}", "p6", "adc${p0:def\\${p1}}",
"p7", "adc\\${"); "p7", "adc\\${");
assertThat(this.parser.replacePlaceholders(text, resolver)).isEqualTo(expected); assertThat(this.parser.replacePlaceholders(text, properties::get)).isEqualTo(expected);
} }
static Stream<Arguments> escapedPlaceholders() { static Stream<Arguments> escapedPlaceholders() {
@ -324,18 +298,21 @@ class PlaceholderParserTests {
Arguments.of("${p4}", "adc${p1}"), Arguments.of("${p4}", "adc${p1}"),
Arguments.of("${p5}", "adcv1"), Arguments.of("${p5}", "adcv1"),
Arguments.of("${p6}", "adcdef${p1}"), Arguments.of("${p6}", "adcdef${p1}"),
Arguments.of("${p7}", "adc\\${")); Arguments.of("${p7}", "adc\\${"),
// Double backslash
Arguments.of("DOMAIN\\\\${user.name}", "DOMAIN\\${user.name}"),
// Triple backslash
Arguments.of("triple\\\\\\${backslash}", "triple\\\\${backslash}"),
// Multiple escaped placeholders
Arguments.of("start\\${prop1}middle\\${prop2}end", "start${prop1}middle${prop2}end")
);
} }
@ParameterizedTest(name = "{0} -> {1}") @ParameterizedTest(name = "{0} -> {1}")
@MethodSource("escapedSeparators") @MethodSource("escapedSeparators")
void escapedSeparatorIsNotReplaced(String text, String expected) { void escapedSeparatorIsNotReplaced(String text, String expected) {
Properties properties = new Properties(); Map<String, String> properties = Map.of("first:Name", "John");
properties.setProperty("first:Name", "John"); assertThat(this.parser.replacePlaceholders(text, properties::get)).isEqualTo(expected);
properties.setProperty("nested0", "first");
properties.setProperty("nested1", "Name");
assertThat(this.parser.replacePlaceholders(text, properties::getProperty)).isEqualTo(expected);
} }
static Stream<Arguments> escapedSeparators() { static Stream<Arguments> escapedSeparators() {
@ -345,6 +322,26 @@ class PlaceholderParserTests {
); );
} }
@ParameterizedTest(name = "{0} -> {1}")
@MethodSource("escapedSeparatorsInNestedPlaceholders")
void escapedSeparatorInNestedPlaceholderIsNotReplaced(String text, String expected) {
Map<String, String> properties = Map.of(
"app.environment", "qa",
"app.service", "protocol",
"protocol://host/qa/name", "protocol://example.com/qa/name",
"service/host/qa/name", "https://example.com/qa/name",
"service/host/qa/name:value", "https://example.com/qa/name-value");
assertThat(this.parser.replacePlaceholders(text, properties::get)).isEqualTo(expected);
}
static Stream<Arguments> escapedSeparatorsInNestedPlaceholders() {
return Stream.of(
Arguments.of("${protocol\\://host/${app.environment}/name}", "protocol://example.com/qa/name"),
Arguments.of("${${app.service}\\://host/${app.environment}/name}", "protocol://example.com/qa/name"),
Arguments.of("${service/host/${app.environment}/name:\\value}", "https://example.com/qa/name"),
Arguments.of("${service/host/${name\\:value}/}", "${service/host/${name:value}/}"));
}
} }
@Nested @Nested
@ -354,34 +351,38 @@ class PlaceholderParserTests {
@Test @Test
void textWithCircularReference() { void textWithCircularReference() {
PlaceholderResolver resolver = mockPlaceholderResolver("pL", "${pR}", "pR", "${pL}"); Map<String, String> properties = Map.of(
assertThatThrownBy(() -> this.parser.replacePlaceholders("${pL}", resolver)) "pL", "${pR}",
"pR", "${pL}");
assertThatThrownBy(() -> this.parser.replacePlaceholders("${pL}", properties::get))
.isInstanceOf(PlaceholderResolutionException.class) .isInstanceOf(PlaceholderResolutionException.class)
.hasMessage("Circular placeholder reference 'pL' in value \"${pL}\" <-- \"${pR}\" <-- \"${pL}\""); .hasMessage("Circular placeholder reference 'pL' in value \"${pL}\" <-- \"${pR}\" <-- \"${pL}\"");
} }
@Test @Test
void unresolvablePlaceholderIsReported() { void unresolvablePlaceholderIsReported() {
PlaceholderResolver resolver = mockPlaceholderResolver();
assertThatExceptionOfType(PlaceholderResolutionException.class) assertThatExceptionOfType(PlaceholderResolutionException.class)
.isThrownBy(() -> this.parser.replacePlaceholders("${bogus}", resolver)) .isThrownBy(() -> this.parser.replacePlaceholders("X${bogus}Z", placeholderName -> null))
.withMessage("Could not resolve placeholder 'bogus' in value \"${bogus}\"") .withMessage("Could not resolve placeholder 'bogus' in value \"X${bogus}Z\"")
.withNoCause(); .withNoCause();
} }
@Test @Test
void unresolvablePlaceholderInNestedPlaceholderIsReportedWithChain() { void unresolvablePlaceholderInNestedPlaceholderIsReportedWithChain() {
PlaceholderResolver resolver = mockPlaceholderResolver("p1", "v1", "p2", "v2", Map<String, String> properties = Map.of(
"p1", "v1",
"p2", "v2",
"p3", "${p1}:${p2}:${bogus}"); "p3", "${p1}:${p2}:${bogus}");
assertThatExceptionOfType(PlaceholderResolutionException.class) assertThatExceptionOfType(PlaceholderResolutionException.class)
.isThrownBy(() -> this.parser.replacePlaceholders("${p3}", resolver)) .isThrownBy(() -> this.parser.replacePlaceholders("${p3}", properties::get))
.withMessage("Could not resolve placeholder 'bogus' in value \"${p1}:${p2}:${bogus}\" <-- \"${p3}\"") .withMessage("Could not resolve placeholder 'bogus' in value \"${p1}:${p2}:${bogus}\" <-- \"${p3}\"")
.withNoCause(); .withNoCause();
} }
} }
PlaceholderResolver mockPlaceholderResolver(String... pairs) {
private static PlaceholderResolver mockPlaceholderResolver(String... pairs) {
if (pairs.length % 2 == 1) { if (pairs.length % 2 == 1) {
throw new IllegalArgumentException("size must be even, it is a set of key=value pairs"); throw new IllegalArgumentException("size must be even, it is a set of key=value pairs");
} }
@ -394,7 +395,7 @@ class PlaceholderParserTests {
return resolver; return resolver;
} }
void verifyPlaceholderResolutions(PlaceholderResolver mock, String... placeholders) { private static void verifyPlaceholderResolutions(PlaceholderResolver mock, String... placeholders) {
InOrder ordered = inOrder(mock); InOrder ordered = inOrder(mock);
for (String placeholder : placeholders) { for (String placeholder : placeholders) {
ordered.verify(mock).resolvePlaceholder(placeholder); ordered.verify(mock).resolvePlaceholder(placeholder);

Loading…
Cancel
Save