批次1: 项目结构 + SQLite 存储层 + 数据模型 批次2: REST API (http.server) 批次3: LLM 编译器 (支持 OpenAI/Anthropic) 批次4: RestrictedPython 规则执行器 批次5: 规则匹配器 + LLM Callback 兜底 批次6: 冲突检测器 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
111 lines
3.2 KiB
Python
111 lines
3.2 KiB
Python
"""Tests for rule executor."""
|
|
import pytest
|
|
from rule_engine.executor import RuleExecutor, Sandbox, ExecutionTimeout
|
|
|
|
|
|
def test_executor_simple_rule():
|
|
"""验证简单规则执行。"""
|
|
executor = RuleExecutor()
|
|
code = '''
|
|
def rule(facts):
|
|
if facts.get("subscription") == "premium":
|
|
return {"action": "discount", "rate": 0.8}
|
|
return None
|
|
'''
|
|
# 匹配
|
|
result = executor.execute_rule(code, {"subscription": "premium", "age": 25})
|
|
assert result == {"action": "discount", "rate": 0.8}
|
|
|
|
# 不匹配
|
|
result = executor.execute_rule(code, {"subscription": "basic", "age": 25})
|
|
assert result is None
|
|
|
|
|
|
def test_executor_complex_condition():
|
|
"""验证复杂条件规则。"""
|
|
executor = RuleExecutor()
|
|
code = '''
|
|
def rule(facts):
|
|
age = facts.get("age", 0)
|
|
subscription = facts.get("subscription")
|
|
if subscription == "premium" and age >= 18:
|
|
return {"action": "approve"}
|
|
return {"action": "deny"}
|
|
'''
|
|
result = executor.execute_rule(code, {"subscription": "premium", "age": 25})
|
|
assert result["action"] == "approve"
|
|
|
|
result = executor.execute_rule(code, {"subscription": "premium", "age": 16})
|
|
assert result["action"] == "deny"
|
|
|
|
result = executor.execute_rule(code, {"subscription": "basic", "age": 25})
|
|
assert result["action"] == "deny"
|
|
|
|
|
|
def test_executor_validate_rule_code_valid():
|
|
"""验证合法代码通过检查。"""
|
|
executor = RuleExecutor()
|
|
code = 'def rule(facts):\n return None'
|
|
assert executor.validate_rule_code(code) is True
|
|
|
|
|
|
def test_executor_validate_rule_code_blocked_keyword():
|
|
"""验证危险关键字被拦截。"""
|
|
executor = RuleExecutor()
|
|
assert executor.validate_rule_code('def rule(facts):\n import os') is False
|
|
assert executor.validate_rule_code('def rule(facts):\n exec("")') is False
|
|
assert executor.validate_rule_code('def rule(facts):\n eval("")') is False
|
|
|
|
|
|
def test_executor_validate_rule_code_syntax_error():
|
|
"""验证语法错误被检测。"""
|
|
executor = RuleExecutor()
|
|
assert executor.validate_rule_code('def rule(facts):\n if .') is False
|
|
|
|
|
|
def test_executor_timeout():
|
|
"""验证执行超时。"""
|
|
executor = RuleExecutor(timeout_ms=100)
|
|
code = '''
|
|
def rule(facts):
|
|
# 死循环
|
|
while True:
|
|
pass
|
|
return None
|
|
'''
|
|
with pytest.raises(RuntimeError) as exc_info:
|
|
executor.execute_rule(code, {})
|
|
assert "timed out" in str(exc_info.value)
|
|
|
|
|
|
def test_sandbox_blocks_dangerous_operations():
|
|
"""验证沙箱拦截危险操作。"""
|
|
executor = RuleExecutor()
|
|
# 尝试访问受保护属性
|
|
code = '''
|
|
def rule(facts):
|
|
return __builtins__
|
|
'''
|
|
# 注意:实际 exec 中 __builtins__ 已被替换为白名单
|
|
# 所以不会返回真正的 builtins
|
|
|
|
|
|
def test_executor_nested_data():
|
|
"""验证嵌套数据结构处理。"""
|
|
executor = RuleExecutor()
|
|
code = '''
|
|
def rule(facts):
|
|
user = facts.get("user", {})
|
|
if user.get("subscription") == "premium" and user.get("active"):
|
|
return {"action": "allow"}
|
|
return None
|
|
'''
|
|
facts = {
|
|
"user": {
|
|
"subscription": "premium",
|
|
"active": True
|
|
}
|
|
}
|
|
result = executor.execute_rule(code, facts)
|
|
assert result["action"] == "allow"
|