feat(condition): CONDITION 判断方式二选一——EXPRESSION 内置 Aviator 表达式(沙箱)+ SERVICE 原口径;JudgeBody 补 submitterId
- ExpressionEvaluator:Aviator 5.4.3 沙箱(FEATURE_SET 只留 If、ALLOWED_CLASS_SET 空=禁类访问/反射)、编译缓存、 顶层变量缺失抛错挂起(Aviator 缺失变量按 nil 且 nil 视作最小值,<= 兜底出口会静默放行——宁挂起不误放行) - ConditionRouteService.decide 分流:EXPRESSION 按出边顺序取首个 true(逐边结果摘要落 raw_response), 全 false/异常挂起沿用 SERVICE 纪律(route_log outlet=NULL + EXPRESSION 常量 + 告警 + 管理端重跑) - DefValidator 发布期分层校验:EXPRESSION 出口缺条件/语法错/违禁表达式硬拦;表单变量放行(运行时核对); 内置变量疑似拼错 warn;judgeMode 非法值拦 - /admin/router/eval 调试端点(同一求值核心,不落库不走流程) - 单测 22 新增 + 全量 170 绿;E2E 18/18(发布/分支选路/挂起留痕/重跑/调试)见 local-debug/four-topics-a-e2e.py
This commit is contained in:
@@ -0,0 +1,108 @@
|
||||
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<String, Object> 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<String, Object> 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<String, Object> 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<String, Object> env = new HashMap<>();
|
||||
env.put("form", new HashMap<String, Object>());
|
||||
Object r = ExpressionEvaluator.evaluate("form.amount == nil", env);
|
||||
assertTrue((Boolean) r);
|
||||
}
|
||||
|
||||
@Test
|
||||
void nonBooleanResult_notTreatedAsTrue() {
|
||||
Map<String, Object> 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<String, Object> 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<String, Object> 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<String, Object> env = new HashMap<>();
|
||||
env.put("submitterId", 990001);
|
||||
assertTrue((Boolean) ExpressionEvaluator.evaluate("submitterId == 990001", env));
|
||||
}
|
||||
|
||||
@Test
|
||||
void syntaxError_thrownAtCompile() {
|
||||
assertThrows(Exception.class, () -> ExpressionEvaluator.compile("amount >"));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user