Files
张翔 1e3dc11d59 refactor(test): 重构测试套件结构并优化测试配置
feat(test-suite): 新增测试套件模块,包含API测试客户端和测试配置
fix(api): 修复数据库实体和仓库的删除操作返回值
style(api): 统一数据库表名和字段命名
perf(api): 添加缓存注解提升配置查询性能
test(api): 添加H2测试数据库配置支持
chore: 清理旧的测试文件和脚本
2026-04-01 20:57:24 +08:00

226 lines
7.2 KiB
Python

# API 集成测试 - 基础API客户端
import pytest
import requests
import time
import os
from typing import Optional, Dict, Any, Union
from requests.adapters import HTTPAdapter, Retry
from dotenv import load_dotenv
import httpx
# 加载环境变量
load_dotenv()
class BaseAPIClient:
"""基础 API 客户端,提供通用的 HTTP 请求方法"""
def __init__(self, base_url: Optional[str] = None, timeout: int = 30):
self.base_url = base_url or os.getenv('BASE_URL', 'http://localhost:8084')
self.timeout = timeout
self.session = requests.Session()
self.token: Optional[str] = None
self.user_id: Optional[int] = None
# 配置重试策略
retries = Retry(
total=3,
backoff_factor=0.1,
status_forcelist=[500, 502, 503, 504]
)
self.session.mount('http', HTTPAdapter(max_retries=retries))
self.session.mount('https', HTTPAdapter(max_retries=retries))
def login(self, username: str, password: str) -> bool:
"""登录并获取 Token"""
response = self.post(
'/api/auth/login',
json={'username': username, 'password': password},
include_auth=False
)
if response.status_code == 200:
data = response.json()
self.token = data.get('token')
self.user_id = data.get('userId')
print(f"✅ 登录成功: {username} (User ID: {self.user_id})")
return True
else:
print(f"❌ 登录失败: {response.status_code}")
return False
def _build_url(self, path: str) -> str:
"""构建完整 URL"""
if path.startswith('http'):
return path
return f"{self.base_url}{path}"
def _get_headers(self, include_auth: bool = True) -> Dict[str, str]:
"""获取请求头"""
headers = {
'Content-Type': 'application/json',
'Accept': 'application/json'
}
if include_auth and self.token:
headers['Authorization'] = f"Bearer {self.token}"
return headers
def get(self, path: str, params: Optional[Dict] = None, include_auth: bool = True) -> requests.Response:
"""GET 请求"""
url = self._build_url(path)
headers = self._get_headers(include_auth)
response = self.session.get(
url,
headers=headers,
params=params,
timeout=self.timeout
)
return response
def post(self, path: str, data: Optional[Dict] = None, json: Optional[Dict] = None,
include_auth: bool = True) -> requests.Response:
"""POST 请求"""
url = self._build_url(path)
headers = self._get_headers(include_auth)
response = self.session.post(
url,
headers=headers,
data=data,
json=json,
timeout=self.timeout
)
return response
def put(self, path: str, data: Optional[Dict] = None, json: Optional[Dict] = None,
include_auth: bool = True) -> requests.Response:
"""PUT 请求"""
url = self._build_url(path)
headers = self._get_headers(include_auth)
response = self.session.put(
url,
headers=headers,
data=data,
json=json,
timeout=self.timeout
)
return response
def delete(self, path: str, include_auth: bool = True) -> requests.Response:
"""DELETE 请求"""
url = self._build_url(path)
headers = self._get_headers(include_auth)
response = self.session.delete(
url,
headers=headers,
timeout=self.timeout
)
return response
def cleanup_resource(self, resource_type: str, resource_id: int) -> bool:
"""清理测试资源"""
try:
response = self.delete(f'/api/{resource_type}/{resource_id}')
return response.status_code == 200
except Exception as e:
print(f"清理资源失败: {e}")
return False
class APIFixture:
"""API 测试固定装置,提供测试数据管理"""
def __init__(self, api_client: BaseAPIClient):
self.api_client = api_client
self.created_users = []
self.created_roles = []
self.created_menus = []
self.created_configs = []
self.created_dicts = []
def cleanup(self):
"""清理所有创建的测试数据"""
print("\n🧹 清理测试数据...")
# 清理用户
for user_id in self.created_users:
self.api_client.cleanup_resource('users', user_id)
self.created_users.clear()
# 清理角色
for role_id in self.created_roles:
self.api_client.cleanup_resource('roles', role_id)
self.created_roles.clear()
# 清理菜单
for menu_id in self.created_menus:
self.api_client.cleanup_resource('menus', menu_id)
self.created_menus.clear()
# 清理配置
for config_id in self.created_configs:
self.api_client.cleanup_resource('config', config_id)
self.created_configs.clear()
# 清理字典
for dict_id in self.created_dicts:
self.api_client.cleanup_resource('dict', dict_id)
self.created_dicts.clear()
print("✅ 测试数据清理完成")
class AsyncAPIClient:
"""异步 API 客户端,使用 httpx"""
def __init__(self, client: httpx.AsyncClient):
self.client = client
self.token: Optional[str] = None
self.user_id: Optional[int] = None
def set_auth(self, token: str, user_id: int = None):
"""设置认证信息"""
self.token = token
self.user_id = user_id
self.client.headers.update({'Authorization': f'Bearer {token}'})
async def login(self, username: str, password: str) -> httpx.Response:
"""登录并获取 Token"""
response = await self.client.post(
'/api/auth/login',
json={'username': username, 'password': password}
)
if response.status_code == 200:
data = response.json()
self.token = data.get('token')
self.user_id = data.get('userId')
print(f"✅ 登录成功: {username} (User ID: {self.user_id})")
return response
async def get(self, path: str, params: Optional[Dict] = None) -> httpx.Response:
"""GET 请求"""
return await self.client.get(path, params=params)
async def post(self, path: str, json: Optional[Dict] = None) -> httpx.Response:
"""POST 请求"""
return await self.client.post(path, json=json)
async def put(self, path: str, json: Optional[Dict] = None) -> httpx.Response:
"""PUT 请求"""
return await self.client.put(path, json=json)
async def delete(self, path: str) -> httpx.Response:
"""DELETE 请求"""
return await self.client.delete(path)