feat(admin): 添加用户管理相关文件

添加用户管理视图、API和状态管理文件
This commit is contained in:
张翔
2026-03-28 14:37:29 +08:00
commit 08ea5fbe98
1643 changed files with 255646 additions and 0 deletions
@@ -0,0 +1,4 @@
from .test_engine import TestEngine
from .validation_engine import ValidationEngine
__all__ = ["TestEngine", "ValidationEngine"]
@@ -0,0 +1,400 @@
from typing import List, Dict, Any, Optional
from collections import defaultdict
from datetime import datetime
from apitest.models.test_models import (
TestCase, TestResult, TestSuiteResult, HTTPMethod, PerformanceMetrics
)
from apitest.client.api_client import APIClient
from apitest.client.auth_manager import AuthManager
from apitest.core.validation_engine import ValidationEngine
from apitest.models.exceptions import TestRunException, RequestException, ValidationException
class TestEngine:
"""测试引擎"""
def __init__(
self,
api_client: APIClient,
auth_manager: Optional[AuthManager] = None,
validation_engine: Optional[ValidationEngine] = None,
logger=None
):
"""初始化测试引擎
Args:
api_client: API客户端
auth_manager: 认证管理器
validation_engine: 验证引擎
logger: 日志记录器
"""
self.api_client = api_client
self.auth_manager = auth_manager
self.validation_engine = validation_engine or ValidationEngine(logger)
self.logger = logger
self._context: Dict[str, Any] = {}
self._dependency_map: Dict[str, List[str]] = defaultdict(list)
self._reverse_dependency_map: Dict[str, List[str]] = defaultdict(list)
def set_context(self, key: str, value: Any):
"""设置上下文变量
Args:
key: 键
value: 值
"""
self._context[key] = value
if self.logger:
self.logger.debug(f"设置上下文变量: {key}")
def get_context(self, key: str, default: Any = None) -> Any:
"""获取上下文变量
Args:
key: 键
default: 默认值
Returns:
值
"""
return self._context.get(key, default)
def _build_dependency_graph(self, test_cases: List[TestCase]):
"""构建依赖关系图
Args:
test_cases: 测试用例列表
"""
self._dependency_map.clear()
self._reverse_dependency_map.clear()
for test_case in test_cases:
for dep_id in test_case.dependencies:
self._dependency_map[test_case.id].append(dep_id)
self._reverse_dependency_map[dep_id].append(test_case.id)
if self.logger:
self.logger.debug(f"依赖关系图构建完成: {len(self._dependency_map)} 个依赖关系")
def _topological_sort(self, test_cases: List[TestCase]) -> List[TestCase]:
"""拓扑排序测试用例
Args:
test_cases: 测试用例列表
Returns:
排序后的测试用例列表
Raises:
TestRunException: 存在循环依赖时抛出
"""
self._build_dependency_graph(test_cases)
in_degree = {tc.id: 0 for tc in test_cases}
test_case_map = {tc.id: tc for tc in test_cases}
for tc in test_cases:
for dep_id in tc.dependencies:
if dep_id in in_degree:
in_degree[tc.id] += 1
queue = [tc_id for tc_id, degree in in_degree.items() if degree == 0]
result = []
while queue:
current_id = queue.pop(0)
result.append(test_case_map[current_id])
for dependent_id in self._reverse_dependency_map[current_id]:
in_degree[dependent_id] -= 1
if in_degree[dependent_id] == 0:
queue.append(dependent_id)
if len(result) != len(test_cases):
raise TestRunException("存在循环依赖,无法确定测试用例执行顺序")
return result
def _prepare_request_data(self, test_case: TestCase) -> Dict[str, Any]:
"""准备请求数据
Args:
test_case: 测试用例
Returns:
准备好的请求数据
"""
params = test_case.params.copy() if test_case.params else {}
body = test_case.body.copy() if test_case.body else {}
params = self._resolve_context_variables(params)
body = self._resolve_context_variables(body)
return {"params": params, "body": body}
def _resolve_context_variables(self, data: Any) -> Any:
"""解析上下文变量
Args:
data: 数据
Returns:
解析后的数据
"""
if isinstance(data, str):
if data.startswith("${") and data.endswith("}"):
var_name = data[2:-1]
return self.get_context(var_name, data)
return data
elif isinstance(data, dict):
return {k: self._resolve_context_variables(v) for k, v in data.items()}
elif isinstance(data, list):
return [self._resolve_context_variables(item) for item in data]
else:
return data
def _execute_setup(self, test_case: TestCase):
"""执行前置操作
Args:
test_case: 测试用例
"""
if not test_case.setup:
return
setup_type = test_case.setup.get("type")
if setup_type == "set_context":
key = test_case.setup.get("key")
value = test_case.setup.get("value")
self.set_context(key, value)
elif setup_type == "sleep":
import time
time.sleep(test_case.setup.get("seconds", 1))
def _execute_teardown(self, test_case: TestCase):
"""执行后置操作
Args:
test_case: 测试用例
"""
if not test_case.teardown:
return
teardown_type = test_case.teardown.get("type")
if teardown_type == "clear_context":
key = test_case.teardown.get("key")
if key in self._context:
del self._context[key]
elif teardown_type == "sleep":
import time
time.sleep(test_case.teardown.get("seconds", 1))
def _execute_test_case(self, test_case: TestCase) -> TestResult:
"""执行单个测试用例
Args:
test_case: 测试用例
Returns:
测试结果
"""
if self.logger:
self.logger.info(f"执行测试用例: {test_case.name} ({test_case.id})")
try:
self._execute_setup(test_case)
request_data = self._prepare_request_data(test_case)
headers = test_case.headers.copy() if test_case.headers else {}
if test_case.auth_required and self.auth_manager:
auth_headers = self.auth_manager.get_auth_headers()
headers.update(auth_headers)
response_data = self.api_client.request(
method=test_case.method,
endpoint=test_case.endpoint,
headers=headers,
params=request_data.get("params"),
body=request_data.get("body"),
retry_count=test_case.retry_count
)
status_code = response_data["status_code"]
response_body = response_data["response_body"]
response_headers = response_data["response_headers"]
performance = response_data["performance"]
passed, error_message = self.validation_engine.validate_response(
test_case,
status_code,
response_body,
response_headers
)
if passed and test_case.validations:
self._extract_response_data(test_case, response_body)
test_result = TestResult(
test_case=test_case,
passed=passed,
status_code=status_code,
response_body=response_body,
response_headers=response_headers,
error_message=error_message if not passed else None,
performance=performance,
execution_time=performance.response_time / 1000.0,
retry_count=test_case.retry_count
)
self._execute_teardown(test_case)
if self.logger:
if passed:
self.logger.info(f"测试用例通过: {test_case.name}")
else:
self.logger.error(f"测试用例失败: {test_case.name} - {error_message}")
return test_result
except RequestException as e:
if self.logger:
self.logger.error(f"请求异常: {test_case.name} - {str(e)}")
return TestResult(
test_case=test_case,
passed=False,
status_code=0,
response_body=None,
response_headers={},
error_message=f"请求异常: {str(e)}"
)
except Exception as e:
if self.logger:
self.logger.error(f"执行异常: {test_case.name} - {str(e)}")
return TestResult(
test_case=test_case,
passed=False,
status_code=0,
response_body=None,
response_headers={},
error_message=f"执行异常: {str(e)}"
)
def _extract_response_data(self, test_case: TestCase, response_body: Any):
"""提取响应数据到上下文
Args:
test_case: 测试用例
response_body: 响应体
"""
extract_config = test_case.validations or []
for validation in extract_config:
if validation.get("type") == "extract":
field = validation.get("field")
var_name = validation.get("var_name", field)
if isinstance(response_body, dict) and field in response_body:
self.set_context(var_name, response_body[field])
def execute_test_suite(
self,
test_cases: List[TestCase],
stop_on_failure: bool = False
) -> TestSuiteResult:
"""执行测试套件
Args:
test_cases: 测试用例列表
stop_on_failure: 是否在失败时停止
Returns:
测试套件结果
"""
if self.logger:
self.logger.info(f"开始执行测试套件,共 {len(test_cases)} 个测试用例")
self._context.clear()
sorted_test_cases = self._topological_sort(test_cases)
results = []
for test_case in sorted_test_cases:
if not test_case.enabled:
if self.logger:
self.logger.info(f"跳过已禁用的测试用例: {test_case.name}")
continue
result = self._execute_test_case(test_case)
results.append(result)
if not result.passed and stop_on_failure:
if self.logger:
self.logger.warning(f"测试失败,停止执行: {test_case.name}")
break
passed_count = sum(1 for r in results if r.passed)
failed_count = sum(1 for r in results if not r.passed)
skipped_count = len(test_cases) - len(results)
test_suite_result = TestSuiteResult(
suite_name="Test Suite",
total=len(test_cases),
passed=passed_count,
failed=failed_count,
skipped=skipped_count,
results=results,
start_time=datetime.now()
)
if self.logger:
self.logger.info(
f"测试套件执行完成: 通过 {test_suite_result.passed}, "
f"失败 {test_suite_result.failed}, "
f"跳过 {test_suite_result.skipped}"
)
return test_suite_result
def execute_test_cases_by_filter(
self,
test_cases: List[TestCase],
module_filter: Optional[str] = None,
tag_filter: Optional[List[str]] = None,
priority_filter: Optional[int] = None
) -> TestSuiteResult:
"""按过滤条件执行测试用例
Args:
test_cases: 测试用例列表
module_filter: 模块过滤
tag_filter: 标签过滤
priority_filter: 优先级过滤
Returns:
测试套件结果
"""
filtered_cases = []
for test_case in test_cases:
if module_filter and test_case.module != module_filter:
continue
if tag_filter and not any(tag in test_case.tags for tag in tag_filter):
continue
if priority_filter is not None and test_case.priority != priority_filter:
continue
filtered_cases.append(test_case)
if self.logger:
self.logger.info(f"过滤后执行 {len(filtered_cases)} 个测试用例")
return self.execute_test_suite(filtered_cases)
@@ -0,0 +1,337 @@
from typing import Dict, Any, List
import json
import re
from apitest.models.test_models import TestCase, TestResult, PerformanceMetrics
from apitest.models.exceptions import ValidationException
class ValidationEngine:
"""验证引擎"""
def __init__(self, logger=None):
"""初始化验证引擎
Args:
logger: 日志记录器
"""
self.logger = logger
def validate_response(
self,
test_case: TestCase,
status_code: int,
response_body: Any,
response_headers: Dict[str, str]
) -> tuple[bool, str]:
"""验证响应
Args:
test_case: 测试用例
status_code: HTTP状态码
response_body: 响应体
response_headers: 响应头
Returns:
(是否通过, 错误消息)
"""
if not test_case.validations:
return True, ""
for validation in test_case.validations:
passed, error = self._execute_validation(
validation,
status_code,
response_body,
response_headers
)
if not passed:
return False, error
return True, ""
def _execute_validation(
self,
validation: Dict[str, Any],
status_code: int,
response_body: Any,
response_headers: Dict[str, str]
) -> tuple[bool, str]:
"""执行单个验证规则
Args:
validation: 验证规则
status_code: HTTP状态码
response_body: 响应体
response_headers: 响应头
Returns:
(是否通过, 错误消息)
"""
validation_type = validation.get("type")
if validation_type == "status_code":
return self._validate_status_code(validation, status_code)
elif validation_type == "contains":
return self._validate_contains(validation, response_body)
elif validation_type == "equals":
return self._validate_equals(validation, response_body)
elif validation_type == "json_path":
return self._validate_json_path(validation, response_body)
elif validation_type == "regex":
return self._validate_regex(validation, response_body)
elif validation_type == "header":
return self._validate_header(validation, response_headers)
elif validation_type == "response_time":
return self._validate_response_time(validation)
elif validation_type == "schema":
return self._validate_schema(validation, response_body)
else:
return False, f"不支持的验证类型: {validation_type}"
def _validate_status_code(self, validation: Dict[str, Any], status_code: int) -> tuple[bool, str]:
"""验证状态码
Args:
validation: 验证规则
status_code: HTTP状态码
Returns:
(是否通过, 错误消息)
"""
expected_code = validation.get("value")
if status_code == expected_code:
return True, ""
return False, f"状态码验证失败: 期望 {expected_code}, 实际 {status_code}"
def _validate_contains(self, validation: Dict[str, Any], response_body: Any) -> tuple[bool, str]:
"""验证响应体包含指定内容
Args:
validation: 验证规则
response_body: 响应体
Returns:
(是否通过, 错误消息)
"""
expected_value = validation.get("value")
field = validation.get("field")
if field:
if isinstance(response_body, dict):
actual_value = response_body.get(field)
else:
return False, f"响应体不是字典类型,无法访问字段: {field}"
else:
actual_value = response_body
if str(expected_value) in str(actual_value):
return True, ""
return False, f"包含验证失败: 响应体中未找到 '{expected_value}'"
def _validate_equals(self, validation: Dict[str, Any], response_body: Any) -> tuple[bool, str]:
"""验证响应体等于指定值
Args:
validation: 验证规则
response_body: 响应体
Returns:
(是否通过, 错误消息)
"""
expected_value = validation.get("value")
field = validation.get("field")
if field:
if isinstance(response_body, dict):
actual_value = response_body.get(field)
else:
return False, f"响应体不是字典类型,无法访问字段: {field}"
else:
actual_value = response_body
if actual_value == expected_value:
return True, ""
return False, f"相等验证失败: 期望 {expected_value}, 实际 {actual_value}"
def _validate_json_path(self, validation: Dict[str, Any], response_body: Any) -> tuple[bool, str]:
"""验证JSON路径
Args:
validation: 验证规则
response_body: 响应体
Returns:
(是否通过, 错误消息)
"""
path = validation.get("path")
expected_value = validation.get("value")
try:
actual_value = self._get_json_path_value(response_body, path)
if actual_value == expected_value:
return True, ""
return False, f"JSON路径验证失败: {path} 期望 {expected_value}, 实际 {actual_value}"
except (KeyError, IndexError, TypeError) as e:
return False, f"JSON路径访问失败: {path} - {str(e)}"
def _get_json_path_value(self, data: Any, path: str) -> Any:
"""获取JSON路径值
Args:
data: 数据
path: JSON路径
Returns:
路径对应的值
"""
parts = path.split(".")
current = data
for part in parts:
if isinstance(current, dict):
current = current[part]
elif isinstance(current, list) and part.isdigit():
current = current[int(part)]
else:
raise KeyError(f"无法访问路径: {part}")
return current
def _validate_regex(self, validation: Dict[str, Any], response_body: Any) -> tuple[bool, str]:
"""验证正则表达式
Args:
validation: 验证规则
response_body: 响应体
Returns:
(是否通过, 错误消息)
"""
pattern = validation.get("pattern")
field = validation.get("field")
if field:
if isinstance(response_body, dict):
actual_value = str(response_body.get(field, ""))
else:
actual_value = str(response_body)
else:
actual_value = str(response_body)
if re.search(pattern, actual_value):
return True, ""
return False, f"正则表达式验证失败: '{actual_value}' 不匹配模式 '{pattern}'"
def _validate_header(self, validation: Dict[str, Any], response_headers: Dict[str, str]) -> tuple[bool, str]:
"""验证响应头
Args:
validation: 验证规则
response_headers: 响应头
Returns:
(是否通过, 错误消息)
"""
header_name = validation.get("name")
expected_value = validation.get("value")
actual_value = response_headers.get(header_name)
if actual_value is None:
return False, f"响应头中未找到: {header_name}"
if expected_value and actual_value != expected_value:
return False, f"响应头验证失败: {header_name} 期望 {expected_value}, 实际 {actual_value}"
return True, ""
def _validate_response_time(self, validation: Dict[str, Any]) -> tuple[bool, str]:
"""验证响应时间(需要在TestResult中检查)
Args:
validation: 验证规则
Returns:
(是否通过, 错误消息)
"""
max_time = validation.get("max_time")
return True, ""
def _validate_schema(self, validation: Dict[str, Any], response_body: Any) -> tuple[bool, str]:
"""验证响应体结构
Args:
validation: 验证规则
response_body: 响应体
Returns:
(是否通过, 错误消息)
"""
schema = validation.get("schema")
if not isinstance(response_body, dict):
return False, f"响应体不是字典类型,无法验证结构"
for field, field_type in schema.items():
if field not in response_body:
return False, f"响应体中缺少字段: {field}"
actual_type = type(response_body[field]).__name__
expected_type = field_type
if actual_type != expected_type:
return False, f"字段 {field} 类型错误: 期望 {expected_type}, 实际 {actual_type}"
return True, ""
def validate_performance(
self,
performance: PerformanceMetrics,
max_response_time: int
) -> tuple[bool, str]:
"""验证性能指标
Args:
performance: 性能指标
max_response_time: 最大响应时间(毫秒)
Returns:
(是否通过, 错误消息)
"""
if performance.response_time > max_response_time:
return False, f"响应时间超过阈值: {performance.response_time}ms > {max_response_time}ms"
return True, ""
def validate_test_result(
self,
test_result: TestResult,
max_response_time: int
) -> tuple[bool, str]:
"""验证测试结果
Args:
test_result: 测试结果
max_response_time: 最大响应时间(毫秒)
Returns:
(是否通过, 错误消息)
"""
if not test_result.passed:
return False, test_result.error_message or "测试失败"
if test_result.performance:
passed, error = self.validate_performance(test_result.performance, max_response_time)
if not passed:
return False, error
return True, ""