531 lines
19 KiB
Python
531 lines
19 KiB
Python
"""规则执行引擎
|
||
|
||
该模块负责执行不同类型的规则,包括Python代码规则、断言规则等,
|
||
支持在API测试的不同生命周期阶段(请求准备、执行、响应验证等)执行规则。
|
||
"""
|
||
|
||
import logging
|
||
import importlib
|
||
import inspect
|
||
import time
|
||
import threading
|
||
from typing import Dict, List, Any, Optional, Union, Callable
|
||
|
||
from ..models.rule_models import (
|
||
AnyRule, BaseRule, RuleCategory, TargetType, RuleLifecycle, RuleScope,
|
||
PythonCodeRule, BusinessAssertionTemplate, PerformanceRule, SecurityRule,
|
||
RESTfulDesignRule, ErrorHandlingRule
|
||
)
|
||
from ..api_caller.caller import APIRequest, APIResponse
|
||
from ..rule_repository.repository import RuleRepository
|
||
|
||
class RuleExecutionError(Exception):
|
||
"""规则执行过程中发生的错误"""
|
||
pass
|
||
|
||
class RuleExecutionResult:
|
||
"""规则执行结果"""
|
||
|
||
def __init__(self,
|
||
rule: BaseRule,
|
||
is_valid: bool,
|
||
message: str = "",
|
||
details: Optional[Dict[str, Any]] = None,
|
||
error: Optional[Exception] = None):
|
||
"""
|
||
初始化规则执行结果
|
||
|
||
Args:
|
||
rule: 执行的规则
|
||
is_valid: 验证是否通过
|
||
message: 执行结果消息
|
||
details: 详细信息
|
||
error: 执行过程中发生的异常(如果有)
|
||
"""
|
||
self.rule = rule
|
||
self.rule_id = rule.id
|
||
self.rule_name = rule.name
|
||
self.rule_category = rule.category
|
||
self.is_valid = is_valid
|
||
self.message = message
|
||
self.details = details or {}
|
||
self.error = error
|
||
|
||
def __bool__(self):
|
||
"""允许直接使用结果对象作为布尔值,表示验证是否通过"""
|
||
return self.is_valid
|
||
|
||
def to_dict(self) -> Dict[str, Any]:
|
||
"""将结果转换为字典"""
|
||
return {
|
||
'rule_id': self.rule_id,
|
||
'rule_name': self.rule_name,
|
||
'rule_category': self.rule_category.value,
|
||
'is_valid': self.is_valid,
|
||
'message': self.message,
|
||
'details': self.details,
|
||
'error': str(self.error) if self.error else None
|
||
}
|
||
|
||
class RuleExecutor:
|
||
"""规则执行引擎"""
|
||
|
||
def __init__(self, rule_repository: RuleRepository):
|
||
"""
|
||
初始化规则执行引擎
|
||
|
||
Args:
|
||
rule_repository: 规则库实例
|
||
"""
|
||
self.rule_repository = rule_repository
|
||
self.logger = logging.getLogger(__name__)
|
||
|
||
def execute_rule(self, rule: BaseRule, context: Dict[str, Any]) -> RuleExecutionResult:
|
||
"""
|
||
执行单个规则
|
||
|
||
Args:
|
||
rule: 要执行的规则
|
||
context: 执行上下文,包含API请求、响应等信息
|
||
|
||
Returns:
|
||
执行结果
|
||
"""
|
||
if not rule.is_enabled:
|
||
return RuleExecutionResult(
|
||
rule=rule,
|
||
is_valid=True,
|
||
message=f"规则 {rule.id} 已禁用,跳过执行"
|
||
)
|
||
|
||
try:
|
||
# 根据规则类型选择适当的执行方法
|
||
if rule.category == RuleCategory.PYTHON_CODE or hasattr(rule, 'code') and rule.code:
|
||
return self._execute_python_code_rule(rule, context)
|
||
elif rule.category == RuleCategory.BUSINESS_LOGIC:
|
||
return self._execute_business_assertion_rule(rule, context)
|
||
elif rule.category == RuleCategory.PERFORMANCE:
|
||
return self._execute_performance_rule(rule, context)
|
||
elif rule.category == RuleCategory.SECURITY:
|
||
return self._execute_security_rule(rule, context)
|
||
elif rule.category == RuleCategory.API_DESIGN:
|
||
return self._execute_api_design_rule(rule, context)
|
||
elif rule.category == RuleCategory.ERROR_HANDLING:
|
||
return self._execute_error_handling_rule(rule, context)
|
||
else:
|
||
return RuleExecutionResult(
|
||
rule=rule,
|
||
is_valid=False,
|
||
message=f"不支持的规则类型: {rule.category}"
|
||
)
|
||
except Exception as e:
|
||
self.logger.error(f"执行规则 {rule.id} 失败: {e}", exc_info=True)
|
||
return RuleExecutionResult(
|
||
rule=rule,
|
||
is_valid=False,
|
||
message=f"规则执行失败: {e}",
|
||
error=e
|
||
)
|
||
|
||
def _execute_python_code_rule(self, rule: BaseRule, context: Dict[str, Any]) -> RuleExecutionResult:
|
||
"""执行Python代码规则"""
|
||
# 获取规则中的Python代码
|
||
code = getattr(rule, 'code', None)
|
||
if not code:
|
||
return RuleExecutionResult(
|
||
rule=rule,
|
||
is_valid=False,
|
||
message="规则未提供Python代码"
|
||
)
|
||
|
||
# 准备执行环境
|
||
namespace = {'__builtins__': __builtins__}
|
||
namespace.update(context)
|
||
|
||
try:
|
||
# 编译并执行代码
|
||
compiled_code = compile(code, f"<rule:{rule.id}>", 'exec')
|
||
exec(compiled_code, namespace)
|
||
|
||
# 查找并执行验证函数
|
||
entry_function = 'validate'
|
||
if hasattr(rule, 'entry_function') and rule.entry_function:
|
||
entry_function = rule.entry_function
|
||
|
||
if entry_function not in namespace:
|
||
return RuleExecutionResult(
|
||
rule=rule,
|
||
is_valid=False,
|
||
message=f"找不到入口函数 '{entry_function}'"
|
||
)
|
||
|
||
validate_func = namespace[entry_function]
|
||
if not callable(validate_func):
|
||
return RuleExecutionResult(
|
||
rule=rule,
|
||
is_valid=False,
|
||
message=f"'{entry_function}' 不是可调用的函数"
|
||
)
|
||
|
||
# 调用验证函数
|
||
timeout = getattr(rule, 'timeout', 5) # 默认5秒超时
|
||
result = self._execute_with_timeout(validate_func, (context,), {}, timeout)
|
||
|
||
# 处理执行结果
|
||
if isinstance(result, dict):
|
||
return RuleExecutionResult(
|
||
rule=rule,
|
||
is_valid=bool(result.get('is_valid', False)),
|
||
message=result.get('message', ''),
|
||
details=result.get('details', {})
|
||
)
|
||
else:
|
||
return RuleExecutionResult(
|
||
rule=rule,
|
||
is_valid=bool(result),
|
||
message=str(result) if result is not True else "验证通过"
|
||
)
|
||
|
||
except Exception as e:
|
||
self.logger.error(f"执行Python代码规则 {rule.id} 失败: {e}", exc_info=True)
|
||
return RuleExecutionResult(
|
||
rule=rule,
|
||
is_valid=False,
|
||
message=f"执行代码失败: {e}",
|
||
error=e
|
||
)
|
||
|
||
def _execute_with_timeout(self, func: Callable, args: tuple, kwargs: Dict[str, Any], timeout: int) -> Any:
|
||
"""使用超时执行函数"""
|
||
result = [None]
|
||
exception = [None]
|
||
|
||
def target():
|
||
try:
|
||
result[0] = func(*args, **kwargs)
|
||
except Exception as e:
|
||
exception[0] = e
|
||
|
||
thread = threading.Thread(target=target)
|
||
thread.daemon = True
|
||
|
||
thread.start()
|
||
thread.join(timeout)
|
||
|
||
if thread.is_alive():
|
||
raise RuleExecutionError(f"规则执行超时(超过{timeout}秒)")
|
||
|
||
if exception[0]:
|
||
raise exception[0]
|
||
|
||
return result[0]
|
||
|
||
def _execute_business_assertion_rule(self, rule: BusinessAssertionTemplate, context: Dict[str, Any]) -> RuleExecutionResult:
|
||
"""执行业务断言规则"""
|
||
# 验证是否提供了所有必需的参数
|
||
if rule.expected_parameters:
|
||
missing_params = [p for p in rule.expected_parameters if p not in context]
|
||
if missing_params:
|
||
return RuleExecutionResult(
|
||
rule=rule,
|
||
is_valid=False,
|
||
message=f"缺少必需的参数: {', '.join(missing_params)}"
|
||
)
|
||
|
||
if rule.template_language == "python_expression":
|
||
try:
|
||
# 使用eval执行Python表达式
|
||
result = eval(rule.template_expression, {'__builtins__': __builtins__}, context)
|
||
return RuleExecutionResult(
|
||
rule=rule,
|
||
is_valid=bool(result),
|
||
message="断言通过" if result else "断言失败"
|
||
)
|
||
except Exception as e:
|
||
self.logger.error(f"执行Python表达式断言失败: {e}", exc_info=True)
|
||
return RuleExecutionResult(
|
||
rule=rule,
|
||
is_valid=False,
|
||
message=f"表达式执行失败: {e}",
|
||
error=e
|
||
)
|
||
else:
|
||
return RuleExecutionResult(
|
||
rule=rule,
|
||
is_valid=False,
|
||
message=f"不支持的模板语言: {rule.template_language}"
|
||
)
|
||
|
||
def _execute_performance_rule(self, rule: PerformanceRule, context: Dict[str, Any]) -> RuleExecutionResult:
|
||
"""执行性能规则"""
|
||
response = context.get('api_response')
|
||
if not response or not isinstance(response, APIResponse):
|
||
return RuleExecutionResult(
|
||
rule=rule,
|
||
is_valid=False,
|
||
message="缺少有效的API响应对象"
|
||
)
|
||
|
||
# 获取响应时间(毫秒)
|
||
elapsed_time = response.elapsed_time * 1000 # 转换为毫秒
|
||
|
||
# 检查是否超过阈值
|
||
if elapsed_time > rule.threshold:
|
||
return RuleExecutionResult(
|
||
rule=rule,
|
||
is_valid=False,
|
||
message=f"响应时间({elapsed_time:.2f}ms)超过阈值({rule.threshold}{rule.unit})",
|
||
details={
|
||
'actual_time': elapsed_time,
|
||
'threshold': rule.threshold,
|
||
'unit': rule.unit
|
||
}
|
||
)
|
||
|
||
return RuleExecutionResult(
|
||
rule=rule,
|
||
is_valid=True,
|
||
message=f"响应时间({elapsed_time:.2f}ms)在阈值范围内",
|
||
details={
|
||
'actual_time': elapsed_time,
|
||
'threshold': rule.threshold,
|
||
'unit': rule.unit
|
||
}
|
||
)
|
||
|
||
def _execute_security_rule(self, rule: SecurityRule, context: Dict[str, Any]) -> RuleExecutionResult:
|
||
"""执行安全规则"""
|
||
if rule.check_type == "transport_security":
|
||
request = context.get('api_request')
|
||
if not request or not isinstance(request, APIRequest):
|
||
return RuleExecutionResult(
|
||
rule=rule,
|
||
is_valid=False,
|
||
message="缺少有效的API请求对象"
|
||
)
|
||
|
||
url = str(request.url)
|
||
|
||
# 检查URL是否使用HTTPS
|
||
if not url.startswith('https://'):
|
||
return RuleExecutionResult(
|
||
rule=rule,
|
||
is_valid=False,
|
||
message="API请求必须使用HTTPS协议",
|
||
details={
|
||
'current_url': url,
|
||
'expected_protocol': 'https'
|
||
}
|
||
)
|
||
|
||
return RuleExecutionResult(
|
||
rule=rule,
|
||
is_valid=True,
|
||
message="API请求使用了HTTPS协议",
|
||
details={
|
||
'url': url
|
||
}
|
||
)
|
||
else:
|
||
return RuleExecutionResult(
|
||
rule=rule,
|
||
is_valid=False,
|
||
message=f"不支持的安全检查类型: {rule.check_type}"
|
||
)
|
||
|
||
def _execute_api_design_rule(self, rule: RESTfulDesignRule, context: Dict[str, Any]) -> RuleExecutionResult:
|
||
"""执行API设计规则"""
|
||
import re
|
||
|
||
request = context.get('api_request')
|
||
if not request or not isinstance(request, APIRequest):
|
||
return RuleExecutionResult(
|
||
rule=rule,
|
||
is_valid=False,
|
||
message="缺少有效的API请求对象"
|
||
)
|
||
|
||
url = str(request.url)
|
||
|
||
# 解析URL,获取路径部分
|
||
from urllib.parse import urlparse
|
||
parsed_url = urlparse(url)
|
||
path = parsed_url.path
|
||
|
||
# 使用正则表达式验证路径
|
||
if rule.pattern and not re.match(rule.pattern, path):
|
||
return RuleExecutionResult(
|
||
rule=rule,
|
||
is_valid=False,
|
||
message=f"API路径不符合{rule.design_aspect}规范",
|
||
details={
|
||
'current_path': path,
|
||
'expected_pattern': rule.pattern
|
||
}
|
||
)
|
||
|
||
return RuleExecutionResult(
|
||
rule=rule,
|
||
is_valid=True,
|
||
message=f"API路径符合{rule.design_aspect}规范",
|
||
details={
|
||
'path': path
|
||
}
|
||
)
|
||
|
||
def _execute_error_handling_rule(self, rule: ErrorHandlingRule, context: Dict[str, Any]) -> RuleExecutionResult:
|
||
"""执行错误处理规则"""
|
||
response = context.get('api_response')
|
||
if not response or not isinstance(response, APIResponse):
|
||
return RuleExecutionResult(
|
||
rule=rule,
|
||
is_valid=False,
|
||
message="缺少有效的API响应对象"
|
||
)
|
||
|
||
# 只验证4xx和5xx状态码
|
||
if response.status_code < 400:
|
||
return RuleExecutionResult(
|
||
rule=rule,
|
||
is_valid=True,
|
||
message="非错误响应,跳过验证",
|
||
details={
|
||
'status_code': response.status_code
|
||
}
|
||
)
|
||
|
||
# 验证状态码是否匹配
|
||
if rule.expected_status != -1 and response.status_code != rule.expected_status:
|
||
return RuleExecutionResult(
|
||
rule=rule,
|
||
is_valid=False,
|
||
message=f"响应状态码({response.status_code})与预期({rule.expected_status})不符",
|
||
details={
|
||
'actual_status': response.status_code,
|
||
'expected_status': rule.expected_status
|
||
}
|
||
)
|
||
|
||
# 验证JSON响应
|
||
if not response.json_content:
|
||
return RuleExecutionResult(
|
||
rule=rule,
|
||
is_valid=False,
|
||
message="错误响应不是有效的JSON格式",
|
||
details={
|
||
'status_code': response.status_code,
|
||
'content_type': response.headers.get('Content-Type', '未知')
|
||
}
|
||
)
|
||
|
||
# 检查错误码
|
||
if rule.error_code != "*" and str(response.json_content.get('code', '')) != rule.error_code:
|
||
return RuleExecutionResult(
|
||
rule=rule,
|
||
is_valid=False,
|
||
message=f"错误码({response.json_content.get('code')})与预期({rule.error_code})不符",
|
||
details={
|
||
'actual_code': response.json_content.get('code'),
|
||
'expected_code': rule.error_code
|
||
}
|
||
)
|
||
|
||
# 验证错误消息
|
||
if rule.expected_message and rule.expected_message not in str(response.json_content.get('message', '')):
|
||
return RuleExecutionResult(
|
||
rule=rule,
|
||
is_valid=False,
|
||
message="错误消息与预期不符",
|
||
details={
|
||
'actual_message': response.json_content.get('message'),
|
||
'expected_message': rule.expected_message
|
||
}
|
||
)
|
||
|
||
return RuleExecutionResult(
|
||
rule=rule,
|
||
is_valid=True,
|
||
message="错误响应符合预期",
|
||
details={
|
||
'status_code': response.status_code,
|
||
'error_code': response.json_content.get('code'),
|
||
'error_message': response.json_content.get('message')
|
||
}
|
||
)
|
||
|
||
def execute_rules_for_lifecycle(self, lifecycle: RuleLifecycle, context: Dict[str, Any]) -> List[RuleExecutionResult]:
|
||
"""
|
||
执行特定生命周期阶段的所有规则
|
||
|
||
Args:
|
||
lifecycle: 生命周期阶段
|
||
context: 执行上下文
|
||
|
||
Returns:
|
||
执行结果列表
|
||
"""
|
||
# 获取适用于该生命周期阶段的所有规则
|
||
rules = self.rule_repository.get_rules_by_lifecycle(lifecycle)
|
||
|
||
# 执行规则
|
||
results = []
|
||
for rule in rules:
|
||
result = self.execute_rule(rule, context)
|
||
results.append(result)
|
||
|
||
return results
|
||
|
||
def execute_rules_for_target(self, target_type: TargetType, target_id: str, context: Dict[str, Any]) -> List[RuleExecutionResult]:
|
||
"""
|
||
执行特定目标的所有规则
|
||
|
||
Args:
|
||
target_type: 目标类型
|
||
target_id: 目标ID
|
||
context: 执行上下文
|
||
|
||
Returns:
|
||
执行结果列表
|
||
"""
|
||
# 获取适用于该目标的所有规则
|
||
rules = self.rule_repository.get_rules_for_target(target_type, target_id)
|
||
|
||
# 执行规则
|
||
results = []
|
||
for rule in rules:
|
||
result = self.execute_rule(rule, context)
|
||
results.append(result)
|
||
|
||
return results
|
||
|
||
def execute_specific_rules(self, rules: List[AnyRule], context: Dict[str, Any], lifecycle_phase: Optional[RuleLifecycle] = None) -> List[RuleExecutionResult]:
|
||
"""
|
||
执行一个明确指定的规则列表。
|
||
|
||
Args:
|
||
rules: 要执行的规则对象的列表。
|
||
context: 执行上下文,包含API请求、响应等信息。
|
||
lifecycle_phase: 可选的,名义上的生命周期阶段,可能用于上下文或某些规则的内部逻辑。
|
||
注意:此参数目前主要用于信息传递,核心执行逻辑在 execute_rule 中
|
||
并不直接依赖它来选择执行路径。
|
||
|
||
Returns:
|
||
一个包含每个规则执行结果的列表。
|
||
"""
|
||
results = []
|
||
if not rules:
|
||
self.logger.info("execute_specific_rules_called_with_no_rules")
|
||
return results
|
||
|
||
# 如果需要,可以将 lifecycle_phase 添加到 context 中传递给每个规则
|
||
# updated_context = context.copy()
|
||
# if lifecycle_phase:
|
||
# updated_context['current_lifecycle_phase'] = lifecycle_phase
|
||
|
||
for rule in rules:
|
||
# 使用现有的 execute_rule 方法执行单个规则
|
||
# result = self.execute_rule(rule, updated_context if lifecycle_phase else context)
|
||
result = self.execute_rule(rule, context) # 简化:暂时不修改context传递
|
||
results.append(result)
|
||
|
||
return results |