refactor(test): 重构测试套件结构并优化测试配置

feat(test-suite): 新增测试套件模块,包含API测试客户端和测试配置
fix(api): 修复数据库实体和仓库的删除操作返回值
style(api): 统一数据库表名和字段命名
perf(api): 添加缓存注解提升配置查询性能
test(api): 添加H2测试数据库配置支持
chore: 清理旧的测试文件和脚本
This commit is contained in:
张翔
2026-04-01 20:57:24 +08:00
parent 24422c2c19
commit 1e3dc11d59
180 changed files with 15421 additions and 3797 deletions
+1
View File
@@ -0,0 +1 @@
"""API模块"""
+72
View File
@@ -0,0 +1,72 @@
"""
审计日志 API 客户端
"""
from httpx import AsyncClient
class AuditLogAPI:
"""审计日志 API 客户端"""
def __init__(self, client: AsyncClient):
self.client = client
async def get_login_log_list(self):
"""获取登录日志列表"""
return await self.client.get('/api/logs/login')
async def get_login_log_by_id(self, log_id):
"""根据ID获取登录日志"""
return await self.client.get(f'/api/logs/login/{log_id}')
async def get_exception_log_list(self):
"""获取异常日志列表"""
return await self.client.get('/api/logs/exception')
async def get_exception_log_by_id(self, log_id):
"""根据ID获取异常日志"""
return await self.client.get(f'/api/logs/exception/{log_id}')
async def get_operation_log_list(self):
"""获取操作日志列表"""
return await self.client.get('/api/logs/operation')
async def get_operation_log_by_id(self, log_id):
"""根据ID获取操作日志"""
return await self.client.get(f'/api/logs/operation/{log_id}')
async def get_login_logs(self, page: int = 0, size: int = 10):
"""分页获取登录日志"""
return await self.client.get(f'/api/logs/login?page={page}&size={size}')
async def get_exception_logs(self, page: int = 0, size: int = 10):
"""分页获取异常日志"""
return await self.client.get(f'/api/logs/exception?page={page}&size={size}')
async def get_operation_logs(self, page: int = 0, size: int = 10, **kwargs):
"""分页获取操作日志,支持筛选参数"""
params = {'page': page, 'size': size}
params.update(kwargs)
return await self.client.get('/api/logs/operation/page', params=params)
async def create_login_log(self, data):
"""创建登录日志"""
return await self.client.post('/api/logs/login', json=data)
async def create_exception_log(self, data):
"""创建异常日志"""
return await self.client.post('/api/logs/exception', json=data)
async def create_operation_log(self, data):
"""创建操作日志"""
return await self.client.post('/api/logs/operation', json=data)
class SysLogAPI(AuditLogAPI):
"""系统日志 API (别名)"""
pass
class AuditAPI(AuditLogAPI):
"""审计 API (别名)"""
pass
+31
View File
@@ -0,0 +1,31 @@
"""
认证 API 客户端
"""
from httpx import AsyncClient
class AuthAPI:
"""认证 API 客户端"""
def __init__(self, client: AsyncClient):
self.client = client
async def login(self, username: str, password: str):
"""登录"""
return await self.client.post('/api/auth/login', json={
'username': username,
'password': password
})
async def register(self, username: str, password: str, email: str):
"""注册"""
return await self.client.post('/api/auth/register', json={
'username': username,
'password': password,
'email': email
})
async def logout(self):
"""登出"""
return await self.client.post('/api/auth/logout')
+225
View File
@@ -0,0 +1,225 @@
# 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)
+45
View File
@@ -0,0 +1,45 @@
"""
系统配置 API 客户端
"""
from httpx import AsyncClient
class ConfigAPI:
"""系统配置 API 客户端"""
def __init__(self, client: AsyncClient):
self.client = client
async def get_config_list(self):
"""获取配置列表"""
return await self.client.get('/api/config')
async def get_config_by_id(self, config_id):
"""根据ID获取配置"""
return await self.client.get(f'/api/config/{config_id}')
async def get_config_by_key(self, config_key):
"""根据key获取配置"""
return await self.client.get(f'/api/config/key/{config_key}')
async def create(self, config_data):
"""创建配置"""
return await self.client.post('/api/config', json=config_data)
async def update(self, config_id, config_data):
"""更新配置"""
return await self.client.put(f'/api/config/{config_id}', json=config_data)
async def delete(self, config_id):
"""删除配置"""
return await self.client.delete(f'/api/config/{config_id}')
async def get_all(self):
"""获取所有配置"""
return await self.client.get('/api/config')
class SysConfigAPI(ConfigAPI):
"""系统配置 API (别名)"""
pass
+64
View File
@@ -0,0 +1,64 @@
"""
字典管理 API 客户端
"""
from httpx import AsyncClient
class DictTypeAPI:
"""字典类型 API 客户端"""
def __init__(self, client: AsyncClient):
self.client = client
async def get_type_list(self, page: int = 0, size: int = 10):
"""获取字典类型列表"""
return await self.client.get(f'/api/dict/types?page={page}&size={size}')
async def get_type_by_id(self, dict_type_id: int):
"""根据ID获取字典类型"""
return await self.client.get(f'/api/dict/types/{dict_type_id}')
async def create(self, dict_type_data):
"""创建字典类型"""
return await self.client.post('/api/dict/types', json=dict_type_data)
async def update(self, dict_type_id: int, dict_type_data):
"""更新字典类型"""
return await self.client.put(f'/api/dict/types/{dict_type_id}', json=dict_type_data)
async def delete(self, dict_type_id: int):
"""删除字典类型"""
return await self.client.delete(f'/api/dict/types/{dict_type_id}')
class DictDataAPI:
"""字典数据 API 客户端"""
def __init__(self, client: AsyncClient):
self.client = client
async def get_dict_list(self, page: int = 0, size: int = 10):
"""获取字典数据列表"""
return await self.client.get(f'/api/dict?page={page}&size={size}')
async def get_dict_by_id(self, dict_id: int):
"""根据ID获取字典数据"""
return await self.client.get(f'/api/dict/{dict_id}')
async def create(self, dict_data):
"""创建字典数据"""
return await self.client.post('/api/dict', json=dict_data)
async def update(self, dict_id: int, dict_data):
"""更新字典数据"""
return await self.client.put(f'/api/dict/{dict_id}', json=dict_data)
async def delete(self, dict_id: int):
"""删除字典数据"""
return await self.client.delete(f'/api/dict/{dict_id}')
class DictAPI(DictTypeAPI, DictDataAPI):
"""字典管理 API (组合)"""
pass
+32
View File
@@ -0,0 +1,32 @@
"""
字典管理 API 客户端
"""
from httpx import AsyncClient
class DictionaryAPI:
"""字典管理 API 客户端"""
def __init__(self, client: AsyncClient):
self.client = client
async def get_dictionary_list(self):
"""获取字典列表"""
return await self.client.get('/api/dictionary')
async def get_dictionary_by_id(self, dictionary_id):
"""根据ID获取字典"""
return await self.client.get(f'/api/dictionary/{dictionary_id}')
async def create_dictionary(self, dictionary_data):
"""创建字典"""
return await self.client.post('/api/dictionary', json=dictionary_data)
async def update_dictionary(self, dictionary_id, dictionary_data):
"""更新字典"""
return await self.client.put(f'/api/dictionary/{dictionary_id}', json=dictionary_data)
async def delete_dictionary(self, dictionary_id):
"""删除字典"""
return await self.client.delete(f'/api/dictionary/{dictionary_id}')
+57
View File
@@ -0,0 +1,57 @@
"""
文件管理 API 客户端
"""
from httpx import AsyncClient
import io
class FileAPI:
"""文件管理 API 客户端"""
def __init__(self, client: AsyncClient):
self.client = client
async def get_file_list(self):
"""获取文件列表"""
return await self.client.get('/api/files')
async def get_file_by_id(self, file_id):
"""根据ID获取文件"""
return await self.client.get(f'/api/files/{file_id}')
async def upload(self, file_path, upload_user):
"""上传文件"""
with open(file_path, 'rb') as f:
return await self.client.post('/api/files/upload', json={'file': file_path, 'uploadUser': upload_user})
async def upload_file(self, file_content, filename, upload_user="test_user"):
"""上传文件(内存方式)"""
files = {'file': (filename, file_content, 'text/plain')}
headers = {'X-Username': upload_user}
return await self.client.post('/api/files/upload', files=files, headers=headers)
async def download(self, file_id):
"""下载文件"""
return await self.client.get(f'/api/files/{file_id}/download')
async def delete(self, file_id):
"""删除文件"""
return await self.client.delete(f'/api/files/{file_id}')
async def get_file_info(self, file_id):
"""获取文件信息(别名)"""
return await self.get_file_by_id(file_id)
async def download_file(self, file_id):
"""下载文件(别名)"""
return await self.download(file_id)
async def delete_file(self, file_id):
"""删除文件(别名)"""
return await self.delete(file_id)
class SysFileAPI(FileAPI):
"""系统文件 API (别名)"""
pass
+20
View File
@@ -0,0 +1,20 @@
# API 集成测试 - 登录测试
import pytest
import sys
import os
# 添加当前目录到Python路径
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from api.base_api import BaseAPIClient
class TestLoginAPI:
"""登录 API 测试"""
def test_login_success(self):
"""测试登录成功"""
client = BaseAPIClient(base_url='http://localhost:8084')
result = client.login('admin', 'admin123')
assert result, "登录应该成功"
assert client.token is not None, "Token 应该被设置"
+44
View File
@@ -0,0 +1,44 @@
"""
菜单管理 API 客户端
"""
from httpx import AsyncClient
class MenuAPI:
"""菜单管理 API 客户端"""
def __init__(self, client: AsyncClient):
self.client = client
async def get_menu_list(self):
"""获取菜单列表"""
return await self.client.get('/api/menus')
async def get_menu_tree(self):
"""获取菜单树"""
return await self.client.get('/api/menus/tree')
async def get_menu_by_id(self, menu_id):
"""根据ID获取菜单"""
return await self.client.get(f'/api/menus/{menu_id}')
async def create_menu(self, menu_data):
"""创建菜单"""
return await self.client.post('/api/menus', json=menu_data)
async def update_menu(self, menu_id, menu_data):
"""更新菜单"""
return await self.client.put(f'/api/menus/{menu_id}', json=menu_data)
async def delete_menu(self, menu_id):
"""删除菜单"""
return await self.client.delete(f'/api/menus/{menu_id}')
async def get_user_menus(self, user_id):
"""获取用户菜单"""
return await self.client.get(f'/api/menus/user/{user_id}')
async def get_user_menus_by_role(self, role_id):
"""获取角色菜单"""
return await self.client.get(f'/api/menus/role/{role_id}')
+50
View File
@@ -0,0 +1,50 @@
"""
通知公告 API 客户端
"""
from httpx import AsyncClient
class NoticeAPI:
"""通知公告 API 客户端"""
def __init__(self, client: AsyncClient):
self.client = client
async def get_notice_list(self):
"""获取公告列表"""
return await self.client.get('/api/notices')
async def get_notice_by_id(self, notice_id):
"""根据ID获取公告"""
return await self.client.get(f'/api/notices/{notice_id}')
async def create(self, notice_data):
"""创建公告"""
return await self.client.post('/api/notices', json=notice_data)
async def update(self, notice_id, notice_data):
"""更新公告"""
return await self.client.put(f'/api/notices/{notice_id}', json=notice_data)
async def delete(self, notice_id):
"""删除公告"""
return await self.client.delete(f'/api/notices/{notice_id}')
async def get_list(self, page: int = 0, size: int = 10):
"""分页获取公告列表"""
return await self.client.get(f'/api/notices?page={page}&size={size}')
async def get_all(self):
"""获取所有公告"""
return await self.client.get('/api/notices/all')
class SysNoticeAPI(NoticeAPI):
"""系统公告 API (别名)"""
pass
class SysMessageAPI(NoticeAPI):
"""系统消息 API (别名)"""
pass
+48
View File
@@ -0,0 +1,48 @@
"""
角色管理 API 客户端
"""
from httpx import AsyncClient
class RoleAPI:
"""角色管理 API 客户端"""
def __init__(self, client: AsyncClient):
self.client = client
async def get_role_list(self):
"""获取角色列表"""
return await self.client.get('/api/roles')
async def get_role_by_id(self, role_id):
"""根据ID获取角色"""
return await self.client.get(f'/api/roles/{role_id}')
async def create_role(self, role_data):
"""创建角色"""
return await self.client.post('/api/roles', json=role_data)
async def update_role(self, role_id, role_data):
"""更新角色"""
return await self.client.put(f'/api/roles/{role_id}', json=role_data)
async def delete_role(self, role_id):
"""删除角色"""
return await self.client.delete(f'/api/roles/{role_id}')
async def get_role_permissions(self, role_id):
"""获取角色权限"""
return await self.client.get(f'/api/roles/{role_id}/permissions')
async def assign_permissions(self, role_id, permission_ids):
"""分配权限"""
return await self.client.post(f'/api/roles/{role_id}/permissions', json={"permissionIds": permission_ids})
async def assign_menus(self, role_id, menu_ids):
"""分配菜单权限(权限分配的别名)"""
return await self.assign_permissions(role_id, menu_ids)
async def get_user_menus_by_role(self, role_id):
"""获取角色菜单(别名方法)"""
return await self.client.get(f'/api/menus/role/{role_id}')
+39
View File
@@ -0,0 +1,39 @@
# API 集成测试 - UAT场景测试
import pytest
import sys
import os
# 添加当前目录到Python路径
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from api.base_api import BaseAPIClient
@pytest.fixture(scope='module')
def api_client():
"""API 客户端 fixture"""
client = BaseAPIClient(base_url='http://localhost:8084')
client.login('admin', 'admin123')
yield client
class TestUATScenario:
"""UAT 场景测试"""
def test_complete_user_workflow(self, api_client: BaseAPIClient):
"""测试完整用户工作流"""
# 1. 创建用户
response = api_client.post(
'/api/users',
json={
'username': 'uat_test_user',
'email': 'uat@test.com',
'password': 'uat123',
'nickname': 'UAT测试用户'
}
)
assert response.status_code == 201, "创建用户应该成功"
# 2. 获取用户列表
response = api_client.get('/api/users')
assert response.status_code == 200, "获取用户列表应该成功"
+50
View File
@@ -0,0 +1,50 @@
"""
用户管理 API 客户端
"""
from httpx import AsyncClient
class UserAPI:
"""用户管理 API 客户端"""
def __init__(self, client: AsyncClient):
self.client = client
async def get_user_list(self):
"""获取用户列表"""
return await self.client.get('/api/users')
async def get_users_by_page(self, page: int = 0, size: int = 10, **kwargs):
"""分页获取用户列表,支持搜索和排序"""
params = {'page': page, 'size': size}
params.update(kwargs)
return await self.client.get('/api/users', params=params)
async def create_user(self, user_data):
"""创建用户"""
return await self.client.post('/api/users', json=user_data)
async def get_user_by_id(self, user_id):
"""根据ID获取用户"""
return await self.client.get(f'/api/users/{user_id}')
async def update_user(self, user_id, user_data):
"""更新用户"""
return await self.client.put(f'/api/users/{user_id}', json=user_data)
async def delete_user(self, user_id):
"""删除用户"""
return await self.client.delete(f'/api/users/{user_id}')
async def get_user_profile(self):
"""获取当前用户资料(调用get_user_by_id,使用token中的userId"""
return await self.client.get('/api/users/profile')
async def update_user_profile(self, profile_data):
"""更新当前用户资料(调用update_user,使用token中的userId"""
return await self.client.put('/api/users/profile', json=profile_data)
async def assign_roles(self, user_id, role_ids):
"""为用户分配角色"""
return await self.client.post(f'/api/users/{user_id}/roles', json=role_ids)