Test status quo for canRead() and read() in SpEL MapAccessor

See gh-35534
This commit is contained in:
Sam Brannen 2025-09-24 16:06:37 +02:00
parent 20aac6dd8d
commit 27b2243b43
1 changed files with 85 additions and 49 deletions

View File

@ -22,71 +22,108 @@ import java.util.Map;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.springframework.expression.Expression; import org.springframework.expression.AccessException;
import org.springframework.expression.spel.standard.SpelCompiler; import org.springframework.expression.spel.standard.SpelCompiler;
import org.springframework.expression.spel.standard.SpelExpressionParser; import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext; import org.springframework.expression.spel.support.StandardEvaluationContext;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
/** /**
* Tests for {@link MapAccessor}. * Tests for {@link MapAccessor}.
* *
* @author Andy Clement * @author Andy Clement
* @author Sam Brannen
*/ */
class MapAccessorTests { class MapAccessorTests {
private final StandardEvaluationContext context = new StandardEvaluationContext();
@Test @Test
void compilationSupport() { void compilationSupport() {
Map<String, Object> testMap = getSimpleTestMap(); context.addPropertyAccessor(new MapAccessor());
StandardEvaluationContext sec = new StandardEvaluationContext();
sec.addPropertyAccessor(new MapAccessor()); var parser = new SpelExpressionParser();
SpelExpressionParser sep = new SpelExpressionParser(); var testMap = getSimpleTestMap();
var nestedMap = getNestedTestMap();
// basic // basic
Expression ex = sep.parseExpression("foo"); var expression = parser.parseExpression("foo");
assertThat(ex.getValue(sec, testMap)).isEqualTo("bar"); assertThat(expression.getValue(context, testMap)).isEqualTo("bar");
assertThat(SpelCompiler.compile(ex)).isTrue(); assertThat(SpelCompiler.compile(expression)).isTrue();
assertThat(ex.getValue(sec, testMap)).isEqualTo("bar"); assertThat(expression.getValue(context, testMap)).isEqualTo("bar");
// compound expression // compound expression
ex = sep.parseExpression("foo.toUpperCase()"); expression = parser.parseExpression("foo.toUpperCase()");
assertThat(ex.getValue(sec, testMap)).isEqualTo("BAR"); assertThat(expression.getValue(context, testMap)).isEqualTo("BAR");
assertThat(SpelCompiler.compile(ex)).isTrue(); assertThat(SpelCompiler.compile(expression)).isTrue();
assertThat(ex.getValue(sec, testMap)).isEqualTo("BAR"); assertThat(expression.getValue(context, testMap)).isEqualTo("BAR");
// nested map // nested map
Map<String, Map<String, Object>> nestedMap = getNestedTestMap(); expression = parser.parseExpression("aaa.foo.toUpperCase()");
ex = sep.parseExpression("aaa.foo.toUpperCase()"); assertThat(expression.getValue(context, nestedMap)).isEqualTo("BAR");
assertThat(ex.getValue(sec, nestedMap)).isEqualTo("BAR"); assertThat(SpelCompiler.compile(expression)).isTrue();
assertThat(SpelCompiler.compile(ex)).isTrue(); assertThat(expression.getValue(context, nestedMap)).isEqualTo("BAR");
assertThat(ex.getValue(sec, nestedMap)).isEqualTo("BAR");
// avoiding inserting checkcast because first part of expression returns a Map // avoiding inserting checkcast because first part of expression returns a Map
ex = sep.parseExpression("getMap().foo"); expression = parser.parseExpression("getMap().foo");
MapGetter mapGetter = new MapGetter(); MapGetter mapGetter = new MapGetter();
assertThat(ex.getValue(sec, mapGetter)).isEqualTo("bar"); assertThat(expression.getValue(context, mapGetter)).isEqualTo("bar");
assertThat(SpelCompiler.compile(ex)).isTrue(); assertThat(SpelCompiler.compile(expression)).isTrue();
assertThat(ex.getValue(sec, mapGetter)).isEqualTo("bar"); assertThat(expression.getValue(context, mapGetter)).isEqualTo("bar");
// basic isWritable // basic isWritable
ex = sep.parseExpression("foo"); expression = parser.parseExpression("foo");
assertThat(ex.isWritable(sec, testMap)).isTrue(); assertThat(expression.isWritable(context, testMap)).isTrue();
// basic write // basic write
ex = sep.parseExpression("foo2"); expression = parser.parseExpression("foo2");
ex.setValue(sec, testMap, "bar2"); expression.setValue(context, testMap, "bar2");
assertThat(ex.getValue(sec, testMap)).isEqualTo("bar2"); assertThat(expression.getValue(context, testMap)).isEqualTo("bar2");
assertThat(SpelCompiler.compile(ex)).isTrue(); assertThat(SpelCompiler.compile(expression)).isTrue();
assertThat(ex.getValue(sec, testMap)).isEqualTo("bar2"); assertThat(expression.getValue(context, testMap)).isEqualTo("bar2");
} }
@Test @Test
void canWrite() throws Exception { void canReadForNonMap() throws AccessException {
StandardEvaluationContext context = new StandardEvaluationContext(); var mapAccessor = new MapAccessor();
Map<String, Object> testMap = getSimpleTestMap();
assertThat(mapAccessor.canRead(context, new Object(), "foo")).isFalse();
}
@Test
void canReadAndReadForExistingKeys() throws AccessException {
var mapAccessor = new MapAccessor();
var map = new HashMap<>();
map.put("foo", null);
map.put("bar", "baz");
assertThat(mapAccessor.canRead(context, map, "foo")).isTrue();
assertThat(mapAccessor.canRead(context, map, "bar")).isTrue();
assertThat(mapAccessor.read(context, map, "foo").getValue()).isNull();
assertThat(mapAccessor.read(context, map, "bar").getValue()).isEqualTo("baz");
}
@Test
void canReadAndReadForNonexistentKeys() throws AccessException {
var mapAccessor = new MapAccessor();
var map = Map.of();
assertThat(mapAccessor.canRead(context, map, "XXX")).isFalse();
assertThatExceptionOfType(AccessException.class)
.isThrownBy(() -> mapAccessor.read(context, map, "XXX").getValue())
.withMessage("Map does not contain a value for key 'XXX'");
}
@Test
void canWrite() throws AccessException {
var mapAccessor = new MapAccessor();
var testMap = getSimpleTestMap();
MapAccessor mapAccessor = new MapAccessor();
assertThat(mapAccessor.canWrite(context, new Object(), "foo")).isFalse(); assertThat(mapAccessor.canWrite(context, new Object(), "foo")).isFalse();
assertThat(mapAccessor.canWrite(context, testMap, "foo")).isTrue(); assertThat(mapAccessor.canWrite(context, testMap, "foo")).isTrue();
// Cannot actually write to an immutable Map, but MapAccessor cannot easily check for that. // Cannot actually write to an immutable Map, but MapAccessor cannot easily check for that.
@ -99,18 +136,17 @@ class MapAccessorTests {
@Test @Test
void isWritable() { void isWritable() {
Map<String, Object> testMap = getSimpleTestMap(); var testMap = getSimpleTestMap();
StandardEvaluationContext sec = new StandardEvaluationContext(); var parser = new SpelExpressionParser();
SpelExpressionParser sep = new SpelExpressionParser(); var expression = parser.parseExpression("foo");
Expression ex = sep.parseExpression("foo");
assertThat(ex.isWritable(sec, testMap)).isFalse(); assertThat(expression.isWritable(context, testMap)).isFalse();
sec.setPropertyAccessors(List.of(new MapAccessor(true))); context.setPropertyAccessors(List.of(new MapAccessor(true)));
assertThat(ex.isWritable(sec, testMap)).isTrue(); assertThat(expression.isWritable(context, testMap)).isTrue();
sec.setPropertyAccessors(List.of(new MapAccessor(false))); context.setPropertyAccessors(List.of(new MapAccessor(false)));
assertThat(ex.isWritable(sec, testMap)).isFalse(); assertThat(expression.isWritable(context, testMap)).isFalse();
} }