package com.yunhai.flowcenter.engine; import org.junit.jupiter.api.Test; import java.util.HashMap; import java.util.Map; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; /** * 表达式求值器单测(2026-10-10 四议题 Phase 4):沙箱纪律(禁类访问/反射/new)、 * 缺失变量补 null、nil 属性语义、非布尔不命中。发布校验/运行时/调试端点共用本核心。 */ class ExpressionEvaluatorTest { @Test void basicComparison_trueAndFalse() { Map env = new HashMap<>(); env.put("amount", 2000); assertTrue((Boolean) ExpressionEvaluator.evaluate("amount > 1000", env)); assertFalse((Boolean) ExpressionEvaluator.evaluate("amount < 1000", env)); } @Test void missingTopLevelVariable_throws_typoFailsClosed() { // 宁挂起不误放行:缺失变量直接抛错(nil 视作最小值,<= 兜底出口会静默放行,不能补 null) Map env = new HashMap<>(); assertThrows(Exception.class, () -> ExpressionEvaluator.evaluate("missing > 1000", env)); assertThrows(Exception.class, () -> ExpressionEvaluator.evaluate("missing <= 1000", env)); } @Test void explicitNullValue_nilComparisons_documentedSemantics() { // env 显式 null 仍走 nil 语义:== nil 可判空;<= 对 nil 为 true(Aviator 视 nil 为最小值) Map env = new HashMap<>(); env.put("amount", null); assertTrue((Boolean) ExpressionEvaluator.evaluate("amount == nil", env)); assertTrue((Boolean) ExpressionEvaluator.evaluate("amount <= 1000", env)); } @Test void nestedMapPropertyMissing_returnsNil() { Map env = new HashMap<>(); env.put("form", new HashMap()); Object r = ExpressionEvaluator.evaluate("form.amount == nil", env); assertTrue((Boolean) r); } @Test void nonBooleanResult_notTreatedAsTrue() { Map env = new HashMap<>(); env.put("amount", 2000); Object r = ExpressionEvaluator.evaluate("amount + 1", env); assertEquals(2001L, ((Number) r).longValue()); assertFalse(Boolean.TRUE.equals(r)); } @Test void stringFunction_allowed() { Map env = new HashMap<>(); env.put("title", "请假申请"); assertTrue((Boolean) ExpressionEvaluator.evaluate("string.contains(title, '请假')", env)); } @Test void sandbox_rejectsNewInstance() { assertThrows(Exception.class, () -> ExpressionEvaluator.evaluate("new java.util.Date()", new HashMap<>())); } @Test void sandbox_rejectsStaticMethodAccess() { assertThrows(Exception.class, () -> ExpressionEvaluator.evaluate("System.currentTimeMillis() > 0", new HashMap<>())); } @Test void sandbox_rejectsChainedReflection() { Map env = new HashMap<>(); env.put("title", "x"); assertThrows(Exception.class, () -> ExpressionEvaluator.evaluate("title.getClass().forName('java.lang.Runtime')", env)); } @Test void sandbox_bareClassReference_isInertVariableLookup() { // 裸类名在属性语法糖下只是嵌套 map 查找(java→lang→Runtime),顶层变量 java 缺失被 // 求值前核对拦下——不构成类访问;真正的类访问(new/静态方法/链式反射)由上面三个用例证明被拒 assertThrows(IllegalStateException.class, () -> ExpressionEvaluator.evaluate("java.lang.Runtime", new HashMap<>())); } @Test void builtinVariableStyle_comparisonAcrossNumberTypes() { Map env = new HashMap<>(); env.put("submitterId", 990001); assertTrue((Boolean) ExpressionEvaluator.evaluate("submitterId == 990001", env)); } @Test void syntaxError_thrownAtCompile() { assertThrows(Exception.class, () -> ExpressionEvaluator.compile("amount >")); } }