2025-05-16 15:18:02 +08:00

531 lines
19 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""规则执行引擎
该模块负责执行不同类型的规则包括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