Files
everything-is-suitable/everything-is-suitable-test/test-tools/core/api_tester.py
T
张翔 08ea5fbe98 feat(admin): 添加用户管理相关文件
添加用户管理视图、API和状态管理文件
2026-03-28 14:37:29 +08:00

284 lines
9.2 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.
"""
API测试器核心模块
"""
import requests
import time
from typing import Dict, Any, Optional, List
from dataclasses import dataclass, field
from .validation import ValidationEngine, ValidationRule
from .auth_manager import AuthManager
from config.settings import config
from utils.logger import TestLogger
from utils.reporter import TestResult as ReportTestResult, TestSummary
@dataclass
class TestResult:
"""测试结果"""
passed: bool
test_name: str
error_message: str = ""
response_time: float = 0.0
status_code: int = 0
request_data: Dict[str, Any] = field(default_factory=dict)
response_data: Dict[str, Any] = field(default_factory=dict)
class APITester:
"""API测试器"""
def __init__(self, logger: TestLogger = None, auto_auth: bool = True):
"""
初始化API测试器
Args:
logger: 日志记录器
auto_auth: 是否自动认证
"""
self.logger = logger or TestLogger("api_tester", config.logging_file, config.logging_level)
self.session = requests.Session()
self.auth_manager = AuthManager(self.logger)
self.validation_engine = ValidationEngine()
self.auto_auth = auto_auth
# 配置会话
self.session.headers.update({
"Content-Type": "application/json",
"Accept": "application/json"
})
# 如果启用自动认证,尝试登录
if auto_auth:
self._ensure_authenticated()
def _ensure_authenticated(self) -> bool:
"""
确保已认证
Returns:
是否认证成功
"""
return self.auth_manager.ensure_authenticated()
def _update_auth_header(self) -> None:
"""更新认证请求头"""
auth_header = self.auth_manager.get_auth_header(auto_refresh=True)
self.session.headers.update(auth_header)
def set_token(self, token: str) -> None:
"""
设置认证令牌(已弃用,建议使用AuthManager
Args:
token: JWT令牌
"""
self.logger.warning("set_token方法已弃用,建议使用AuthManager")
self.session.headers.update({
"Authorization": f"Bearer {token}"
})
self.logger.info(f"已设置认证令牌")
def clear_token(self) -> None:
"""清除认证令牌(已弃用,建议使用AuthManager"""
self.logger.warning("clear_token方法已弃用,建议使用AuthManager")
self.session.headers.pop("Authorization", None)
self.logger.info(f"已清除认证令牌")
def login(self, username: str = None, password: str = None) -> bool:
"""
用户登录
Args:
username: 用户名
password: 密码
Returns:
是否登录成功
"""
return self.auth_manager.login(username, password)
def request(
self,
method: str,
endpoint: str,
data: Dict[str, Any] = None,
params: Dict[str, Any] = None,
headers: Dict[str, str] = None,
expected_status: int = 200,
test_name: str = None,
require_auth: bool = True
) -> TestResult:
"""
发送HTTP请求
Args:
method: HTTP方法(GET, POST, PUT, DELETE
endpoint: API端点
data: 请求体数据
params: URL参数
headers: 请求头
expected_status: 期望的状态码
test_name: 测试名称
require_auth: 是否需要认证
Returns:
测试结果
"""
if test_name is None:
test_name = f"{method} {endpoint}"
url = f"{config.api_base_url}{endpoint}"
# 如果需要认证,确保已认证并更新认证头
if require_auth and self.auto_auth:
if not self._ensure_authenticated():
return TestResult(
passed=False,
test_name=test_name,
error_message="认证失败"
)
self._update_auth_header()
self.logger.log_test_start(test_name)
self.logger.log_request(method, url, data, headers)
try:
start_time = time.time()
if method.upper() == "GET":
response = self.session.get(
url,
params=params,
headers=headers,
timeout=config.api_timeout
)
elif method.upper() == "POST":
response = self.session.post(
url,
json=data,
params=params,
headers=headers,
timeout=config.api_timeout
)
elif method.upper() == "PUT":
response = self.session.put(
url,
json=data,
params=params,
headers=headers,
timeout=config.api_timeout
)
elif method.upper() == "DELETE":
response = self.session.delete(
url,
params=params,
headers=headers,
timeout=config.api_timeout
)
else:
raise ValueError(f"不支持的HTTP方法: {method}")
response_time = (time.time() - start_time) * 1000
try:
response_data = response.json()
except:
response_data = {"raw": response.text}
self.logger.log_response(response.status_code, response_time, response_data)
# 验证状态码
passed, error = self.validation_engine.validate_status_code(expected_status, response.status_code)
if passed:
self.logger.log_test_end(test_name, True, response_time)
return TestResult(
passed=True,
test_name=test_name,
response_time=response_time,
status_code=response.status_code,
request_data=data or params or {},
response_data=response_data
)
else:
self.logger.log_test_end(test_name, False, response_time)
return TestResult(
passed=False,
test_name=test_name,
error_message=error,
response_time=response_time,
status_code=response.status_code,
request_data=data or params or {},
response_data=response_data
)
except requests.exceptions.Timeout:
error_msg = "请求超时"
self.logger.error(f"{test_name} - {error_msg}")
return TestResult(
passed=False,
test_name=test_name,
error_message=error_msg,
response_time=config.api_timeout * 1000
)
except requests.exceptions.ConnectionError:
error_msg = "连接错误"
self.logger.error(f"{test_name} - {error_msg}")
return TestResult(
passed=False,
test_name=test_name,
error_message=error_msg
)
except Exception as e:
self.logger.log_error(e)
return TestResult(
passed=False,
test_name=test_name,
error_message=f"未知错误: {str(e)}"
)
def validate(
self,
test_result: TestResult,
validation_rules: List[ValidationRule]
) -> TestResult:
"""
验证测试结果
Args:
test_result: 测试结果
validation_rules: 验证规则列表
Returns:
验证后的测试结果
"""
if not test_result.passed:
return test_result
for rule in validation_rules:
if rule.rule_type == "status_code":
passed, error = rule.validate(test_result.status_code)
elif rule.rule_type == "response_time":
passed, error = rule.validate(test_result.response_time)
elif rule.rule_type in ["contains", "equals", "json_path", "regex", "header", "schema"]:
passed, error = rule.validate(actual_data=test_result.response_data)
else:
passed, error = False, f"未知的验证规则: {rule.rule_type}"
self.logger.log_validation(rule.rule_type, passed, error)
if not passed:
test_result.passed = False
test_result.error_message = error
break
return test_result
def close(self) -> None:
"""关闭会话"""
self.session.close()
self.logger.info("已关闭测试会话")