refactor(test): 重构测试套件结构并优化测试配置
feat(test-suite): 新增测试套件模块,包含API测试客户端和测试配置 fix(api): 修复数据库实体和仓库的删除操作返回值 style(api): 统一数据库表名和字段命名 perf(api): 添加缓存注解提升配置查询性能 test(api): 添加H2测试数据库配置支持 chore: 清理旧的测试文件和脚本
This commit is contained in:
@@ -0,0 +1,19 @@
|
||||
"""
|
||||
单元测试套件初始化文件
|
||||
|
||||
作者: 张翔
|
||||
日期: 2026-04-01
|
||||
"""
|
||||
|
||||
from .test_utils import TestDateHelper, TestStringHelper, TestValidator
|
||||
from .test_api_clients import TestBaseAPI, TestAuthAPI, TestUserAPI, TestRoleAPI
|
||||
|
||||
__all__ = [
|
||||
"TestDateHelper",
|
||||
"TestStringHelper",
|
||||
"TestValidator",
|
||||
"TestBaseAPI",
|
||||
"TestAuthAPI",
|
||||
"TestUserAPI",
|
||||
"TestRoleAPI",
|
||||
]
|
||||
@@ -0,0 +1,349 @@
|
||||
"""
|
||||
单元测试套件 - API客户端测试
|
||||
|
||||
测试范围:
|
||||
1. API客户端基础功能
|
||||
2. 认证API测试
|
||||
3. 用户API测试
|
||||
4. 角色API测试
|
||||
|
||||
作者: 张翔
|
||||
日期: 2026-04-01
|
||||
"""
|
||||
|
||||
import pytest
|
||||
from unittest.mock import Mock, AsyncMock, patch
|
||||
from api.base_api import BaseAPIClient, AsyncAPIClient
|
||||
from api.auth_api import AuthAPI
|
||||
from api.user_api import UserAPI
|
||||
from api.role_api import RoleAPI
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
class TestBaseAPI:
|
||||
"""API客户端基础功能测试"""
|
||||
|
||||
def test_base_api_initialization(self):
|
||||
"""
|
||||
UNIT-API-01: API客户端初始化测试
|
||||
|
||||
验证点:
|
||||
1. 正确初始化客户端
|
||||
2. 设置基础URL
|
||||
3. 设置超时时间
|
||||
"""
|
||||
base_url = "http://localhost:8084"
|
||||
|
||||
api = BaseAPIClient(base_url=base_url, timeout=30)
|
||||
|
||||
assert api.base_url == base_url
|
||||
assert api.timeout == 30
|
||||
assert api.session is not None
|
||||
|
||||
def test_api_headers_generation(self):
|
||||
"""
|
||||
UNIT-API-02: API请求头生成测试
|
||||
|
||||
验证点:
|
||||
1. 正确生成基础请求头
|
||||
2. 包含认证Token时添加Authorization
|
||||
"""
|
||||
api = BaseAPIClient()
|
||||
api.token = "test_token_123"
|
||||
|
||||
headers = api._get_headers(include_auth=True)
|
||||
|
||||
assert "Content-Type" in headers
|
||||
assert "Authorization" in headers
|
||||
assert headers["Authorization"] == "Bearer test_token_123"
|
||||
|
||||
headers_no_auth = api._get_headers(include_auth=False)
|
||||
assert "Authorization" not in headers_no_auth
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
class TestAuthAPI:
|
||||
"""认证API测试"""
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_login_success(self):
|
||||
"""
|
||||
UNIT-AUTH-01: 登录成功测试
|
||||
|
||||
验证点:
|
||||
1. 正确发送登录请求
|
||||
2. 返回Token
|
||||
3. 返回用户信息
|
||||
"""
|
||||
mock_client = Mock()
|
||||
mock_client.post = AsyncMock(
|
||||
return_value=Mock(
|
||||
status_code=200,
|
||||
json=Mock(return_value={
|
||||
"token": "test_token_123",
|
||||
"userId": 1,
|
||||
"username": "admin"
|
||||
})
|
||||
)
|
||||
)
|
||||
|
||||
auth_api = AuthAPI(mock_client)
|
||||
|
||||
response = await auth_api.login("admin", "admin123")
|
||||
|
||||
assert response.status_code == 200
|
||||
assert response.json()["token"] == "test_token_123"
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_login_failure(self):
|
||||
"""
|
||||
UNIT-AUTH-02: 登录失败测试
|
||||
|
||||
验证点:
|
||||
1. 错误密码返回401
|
||||
2. 错误信息不泄露敏感信息
|
||||
"""
|
||||
mock_client = Mock()
|
||||
mock_client.post = AsyncMock(
|
||||
return_value=Mock(
|
||||
status_code=401,
|
||||
json=Mock(return_value={
|
||||
"error": "Invalid credentials"
|
||||
})
|
||||
)
|
||||
)
|
||||
|
||||
auth_api = AuthAPI(mock_client)
|
||||
|
||||
response = await auth_api.login("admin", "wrong_password")
|
||||
|
||||
assert response.status_code == 401
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_logout(self):
|
||||
"""
|
||||
UNIT-AUTH-03: 登出测试
|
||||
|
||||
验证点:
|
||||
1. 正确发送登出请求
|
||||
2. 返回成功状态
|
||||
"""
|
||||
mock_client = Mock()
|
||||
mock_client.post = AsyncMock(
|
||||
return_value=Mock(status_code=200)
|
||||
)
|
||||
|
||||
auth_api = AuthAPI(mock_client)
|
||||
|
||||
response = await auth_api.logout()
|
||||
|
||||
assert response.status_code == 200
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
class TestUserAPI:
|
||||
"""用户API测试"""
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_users_by_page(self):
|
||||
"""
|
||||
UNIT-USER-01: 分页获取用户列表测试
|
||||
|
||||
验证点:
|
||||
1. 正确发送分页参数
|
||||
2. 返回分页数据
|
||||
3. 包含总数信息
|
||||
"""
|
||||
mock_client = Mock()
|
||||
mock_client.get = AsyncMock(
|
||||
return_value=Mock(
|
||||
status_code=200,
|
||||
json=Mock(return_value={
|
||||
"content": [
|
||||
{"id": 1, "username": "admin"},
|
||||
{"id": 2, "username": "user"}
|
||||
],
|
||||
"totalElements": 2,
|
||||
"totalPages": 1,
|
||||
"size": 10,
|
||||
"number": 0
|
||||
})
|
||||
)
|
||||
)
|
||||
|
||||
user_api = UserAPI(mock_client)
|
||||
|
||||
response = await user_api.get_users_by_page(page=0, size=10)
|
||||
|
||||
assert response.status_code == 200
|
||||
assert len(response.json()["content"]) == 2
|
||||
assert response.json()["totalElements"] == 2
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_create_user(self):
|
||||
"""
|
||||
UNIT-USER-02: 创建用户测试
|
||||
|
||||
验证点:
|
||||
1. 正确发送用户数据
|
||||
2. 返回创建的用户ID
|
||||
3. 验证必填字段
|
||||
"""
|
||||
mock_client = Mock()
|
||||
mock_client.post = AsyncMock(
|
||||
return_value=Mock(
|
||||
status_code=201,
|
||||
json=Mock(return_value={
|
||||
"id": 3,
|
||||
"username": "new_user",
|
||||
"email": "new_user@test.com"
|
||||
})
|
||||
)
|
||||
)
|
||||
|
||||
user_api = UserAPI(mock_client)
|
||||
|
||||
user_data = {
|
||||
"username": "new_user",
|
||||
"password": "Test123!@#",
|
||||
"email": "new_user@test.com",
|
||||
"phone": "13800138000"
|
||||
}
|
||||
|
||||
response = await user_api.create_user(user_data)
|
||||
|
||||
assert response.status_code == 201
|
||||
assert response.json()["id"] == 3
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_update_user(self):
|
||||
"""
|
||||
UNIT-USER-03: 更新用户测试
|
||||
|
||||
验证点:
|
||||
1. 正确发送更新数据
|
||||
2. 返回更新后的用户信息
|
||||
3. 部分更新支持
|
||||
"""
|
||||
mock_client = Mock()
|
||||
mock_client.put = AsyncMock(
|
||||
return_value=Mock(
|
||||
status_code=200,
|
||||
json=Mock(return_value={
|
||||
"id": 1,
|
||||
"email": "updated@test.com"
|
||||
})
|
||||
)
|
||||
)
|
||||
|
||||
user_api = UserAPI(mock_client)
|
||||
|
||||
update_data = {"email": "updated@test.com"}
|
||||
|
||||
response = await user_api.update_user(1, update_data)
|
||||
|
||||
assert response.status_code == 200
|
||||
assert response.json()["email"] == "updated@test.com"
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_delete_user(self):
|
||||
"""
|
||||
UNIT-USER-04: 删除用户测试
|
||||
|
||||
验证点:
|
||||
1. 正确发送删除请求
|
||||
2. 返回成功状态
|
||||
"""
|
||||
mock_client = Mock()
|
||||
mock_client.delete = AsyncMock(
|
||||
return_value=Mock(status_code=204)
|
||||
)
|
||||
|
||||
user_api = UserAPI(mock_client)
|
||||
|
||||
response = await user_api.delete_user(1)
|
||||
|
||||
assert response.status_code == 204
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_search_users(self):
|
||||
"""
|
||||
UNIT-USER-05: 搜索用户测试
|
||||
|
||||
验证点:
|
||||
1. 正确发送搜索关键词
|
||||
2. 返回匹配结果
|
||||
"""
|
||||
mock_client = Mock()
|
||||
mock_client.get = AsyncMock(
|
||||
return_value=Mock(
|
||||
status_code=200,
|
||||
json=Mock(return_value={
|
||||
"content": [
|
||||
{"id": 1, "username": "admin"}
|
||||
],
|
||||
"totalElements": 1
|
||||
})
|
||||
)
|
||||
)
|
||||
|
||||
user_api = UserAPI(mock_client)
|
||||
|
||||
response = await user_api.get_users_by_page(username="admin")
|
||||
|
||||
assert response.status_code == 200
|
||||
assert len(response.json()["content"]) == 1
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
class TestRoleAPI:
|
||||
"""角色API测试"""
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_roles(self):
|
||||
"""
|
||||
UNIT-ROLE-01: 获取角色列表测试
|
||||
|
||||
验证点:
|
||||
1. 正确返回角色列表
|
||||
2. 包含角色权限信息
|
||||
"""
|
||||
mock_client = Mock()
|
||||
mock_client.get = AsyncMock(
|
||||
return_value=Mock(
|
||||
status_code=200,
|
||||
json=Mock(return_value=[
|
||||
{"id": 1, "roleName": "管理员", "roleKey": "admin"},
|
||||
{"id": 2, "roleName": "普通用户", "roleKey": "user"}
|
||||
])
|
||||
)
|
||||
)
|
||||
|
||||
role_api = RoleAPI(mock_client)
|
||||
|
||||
response = await role_api.get_role_list()
|
||||
|
||||
assert response.status_code == 200
|
||||
assert len(response.json()) == 2
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_assign_permissions(self):
|
||||
"""
|
||||
UNIT-ROLE-02: 分配权限测试
|
||||
|
||||
验证点:
|
||||
1. 正确发送权限数据
|
||||
2. 返回成功状态
|
||||
"""
|
||||
mock_client = Mock()
|
||||
mock_client.post = AsyncMock(
|
||||
return_value=Mock(status_code=200)
|
||||
)
|
||||
|
||||
role_api = RoleAPI(mock_client)
|
||||
|
||||
permission_ids = [1, 2, 3]
|
||||
|
||||
response = await role_api.assign_permissions(1, permission_ids)
|
||||
|
||||
assert response.status_code == 200
|
||||
@@ -0,0 +1,332 @@
|
||||
"""
|
||||
单元测试套件 - 工具类测试
|
||||
|
||||
测试范围:
|
||||
1. 日期时间工具类
|
||||
2. 字符串处理工具类
|
||||
3. 数据验证工具类
|
||||
4. 加密工具类
|
||||
|
||||
作者: 张翔
|
||||
日期: 2026-04-01
|
||||
"""
|
||||
|
||||
import pytest
|
||||
from datetime import datetime
|
||||
from utils.date_helper import DateHelper
|
||||
from utils.string_helper import StringHelper
|
||||
from utils.validator import Validator
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
class TestDateHelper:
|
||||
"""日期时间工具类测试"""
|
||||
|
||||
def test_format_datetime(self):
|
||||
"""
|
||||
UNIT-DATE-01: 日期时间格式化测试
|
||||
|
||||
验证点:
|
||||
1. 正确格式化日期时间
|
||||
2. 支持多种格式
|
||||
3. 处理空值
|
||||
"""
|
||||
test_datetime = datetime(2026, 4, 1, 12, 30, 45)
|
||||
|
||||
formatted = DateHelper.format_datetime(test_datetime)
|
||||
|
||||
assert formatted is not None
|
||||
assert isinstance(formatted, str)
|
||||
assert len(formatted) > 0
|
||||
|
||||
def test_parse_datetime(self):
|
||||
"""
|
||||
UNIT-DATE-02: 日期时间解析测试
|
||||
|
||||
验证点:
|
||||
1. 正确解析字符串为日期时间
|
||||
2. 处理无效格式
|
||||
"""
|
||||
date_string = "2026-04-01 12:30:45"
|
||||
|
||||
parsed = DateHelper.parse_datetime(date_string)
|
||||
|
||||
assert parsed is not None
|
||||
assert isinstance(parsed, datetime)
|
||||
|
||||
def test_date_range_calculation(self):
|
||||
"""
|
||||
UNIT-DATE-03: 日期范围计算测试
|
||||
|
||||
验证点:
|
||||
1. 正确计算日期范围
|
||||
2. 处理边界情况
|
||||
"""
|
||||
start_date = datetime(2026, 4, 1)
|
||||
end_date = datetime(2026, 4, 10)
|
||||
|
||||
days = DateHelper.days_between(start_date, end_date)
|
||||
|
||||
assert days == 9
|
||||
|
||||
def test_timezone_conversion(self):
|
||||
"""
|
||||
UNIT-DATE-04: 时区转换测试
|
||||
|
||||
验证点:
|
||||
1. 正确转换时区
|
||||
2. 处理不同时区
|
||||
"""
|
||||
utc_time = datetime(2026, 4, 1, 12, 0, 0)
|
||||
|
||||
local_time = DateHelper.utc_to_local(utc_time)
|
||||
|
||||
assert local_time is not None
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
class TestStringHelper:
|
||||
"""字符串处理工具类测试"""
|
||||
|
||||
def test_truncate_string(self):
|
||||
"""
|
||||
UNIT-STR-01: 字符串截断测试
|
||||
|
||||
验证点:
|
||||
1. 正确截断字符串
|
||||
2. 添加省略号
|
||||
3. 处理短字符串
|
||||
"""
|
||||
long_string = "这是一个非常长的字符串,需要被截断处理"
|
||||
|
||||
truncated = StringHelper.truncate(long_string, max_length=10)
|
||||
|
||||
assert len(truncated) <= 13
|
||||
assert "..." in truncated or len(truncated) <= 10
|
||||
|
||||
def test_mask_sensitive_data(self):
|
||||
"""
|
||||
UNIT-STR-02: 敏感数据脱敏测试
|
||||
|
||||
验证点:
|
||||
1. 正确脱敏手机号
|
||||
2. 正确脱敏邮箱
|
||||
3. 正确脱敏身份证号
|
||||
"""
|
||||
phone = "13800138000"
|
||||
email = "test@example.com"
|
||||
id_card = "110101199001011234"
|
||||
|
||||
masked_phone = StringHelper.mask_phone(phone)
|
||||
masked_email = StringHelper.mask_email(email)
|
||||
masked_id = StringHelper.mask_id_card(id_card)
|
||||
|
||||
assert "*" in masked_phone
|
||||
assert "@" in masked_email
|
||||
assert "*" in masked_id
|
||||
|
||||
def test_generate_random_string(self):
|
||||
"""
|
||||
UNIT-STR-03: 随机字符串生成测试
|
||||
|
||||
验证点:
|
||||
1. 生成指定长度字符串
|
||||
2. 字符串唯一性
|
||||
3. 包含指定字符集
|
||||
"""
|
||||
length = 16
|
||||
|
||||
random_str1 = StringHelper.random_string(length)
|
||||
random_str2 = StringHelper.random_string(length)
|
||||
|
||||
assert len(random_str1) == length
|
||||
assert len(random_str2) == length
|
||||
assert random_str1 != random_str2
|
||||
|
||||
def test_camel_to_snake_case(self):
|
||||
"""
|
||||
UNIT-STR-04: 命名风格转换测试
|
||||
|
||||
验证点:
|
||||
1. 驼峰转下划线
|
||||
2. 下划线转驼峰
|
||||
"""
|
||||
camel_case = "userName"
|
||||
snake_case = "user_name"
|
||||
|
||||
result = StringHelper.camel_to_snake(camel_case)
|
||||
|
||||
assert result == snake_case
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
class TestValidator:
|
||||
"""数据验证工具类测试"""
|
||||
|
||||
def test_validate_email(self):
|
||||
"""
|
||||
UNIT-VAL-01: 邮箱验证测试
|
||||
|
||||
验证点:
|
||||
1. 正确验证邮箱格式
|
||||
2. 拒绝无效邮箱
|
||||
"""
|
||||
valid_emails = [
|
||||
"test@example.com",
|
||||
"user.name@example.co.uk",
|
||||
"user+tag@example.com"
|
||||
]
|
||||
|
||||
invalid_emails = [
|
||||
"invalid-email",
|
||||
"@example.com",
|
||||
"user@",
|
||||
"user @example.com"
|
||||
]
|
||||
|
||||
for email in valid_emails:
|
||||
assert Validator.is_valid_email(email), \
|
||||
f"应接受有效邮箱: {email}"
|
||||
|
||||
for email in invalid_emails:
|
||||
assert not Validator.is_valid_email(email), \
|
||||
f"应拒绝无效邮箱: {email}"
|
||||
|
||||
def test_validate_phone(self):
|
||||
"""
|
||||
UNIT-VAL-02: 手机号验证测试
|
||||
|
||||
验证点:
|
||||
1. 正确验证手机号格式
|
||||
2. 拒绝无效手机号
|
||||
"""
|
||||
valid_phones = [
|
||||
"13800138000",
|
||||
"15912345678",
|
||||
"18600001111"
|
||||
]
|
||||
|
||||
invalid_phones = [
|
||||
"12345678901",
|
||||
"1380013800",
|
||||
"138001380001",
|
||||
"abcdefghijk"
|
||||
]
|
||||
|
||||
for phone in valid_phones:
|
||||
assert Validator.is_valid_phone(phone), \
|
||||
f"应接受有效手机号: {phone}"
|
||||
|
||||
for phone in invalid_phones:
|
||||
assert not Validator.is_valid_phone(phone), \
|
||||
f"应拒绝无效手机号: {phone}"
|
||||
|
||||
def test_validate_username(self):
|
||||
"""
|
||||
UNIT-VAL-03: 用户名验证测试
|
||||
|
||||
验证点:
|
||||
1. 正确验证用户名格式
|
||||
2. 长度限制
|
||||
3. 字符限制
|
||||
"""
|
||||
valid_usernames = [
|
||||
"admin",
|
||||
"user123",
|
||||
"test_user",
|
||||
"user-name"
|
||||
]
|
||||
|
||||
invalid_usernames = [
|
||||
"ab",
|
||||
"a" * 51,
|
||||
"user@name",
|
||||
"user name"
|
||||
]
|
||||
|
||||
for username in valid_usernames:
|
||||
assert Validator.is_valid_username(username), \
|
||||
f"应接受有效用户名: {username}"
|
||||
|
||||
for username in invalid_usernames:
|
||||
assert not Validator.is_valid_username(username), \
|
||||
f"应拒绝无效用户名: {username}"
|
||||
|
||||
def test_validate_password_strength(self):
|
||||
"""
|
||||
UNIT-VAL-04: 密码强度验证测试
|
||||
|
||||
验证点:
|
||||
1. 长度要求
|
||||
2. 复杂度要求
|
||||
3. 常见密码拒绝
|
||||
"""
|
||||
strong_passwords = [
|
||||
"Test123!@#",
|
||||
"StrongP@ssw0rd",
|
||||
"C0mpl3x!Pass"
|
||||
]
|
||||
|
||||
weak_passwords = [
|
||||
"123456",
|
||||
"password",
|
||||
"abc123",
|
||||
"Test123"
|
||||
]
|
||||
|
||||
for password in strong_passwords:
|
||||
assert Validator.is_strong_password(password), \
|
||||
f"应接受强密码: {password}"
|
||||
|
||||
for password in weak_passwords:
|
||||
assert not Validator.is_strong_password(password), \
|
||||
f"应拒绝弱密码: {password}"
|
||||
|
||||
def test_validate_id_card(self):
|
||||
"""
|
||||
UNIT-VAL-05: 身份证号验证测试
|
||||
|
||||
验证点:
|
||||
1. 18位身份证验证
|
||||
2. 校验码验证
|
||||
"""
|
||||
valid_id_cards = [
|
||||
"110101199003077654",
|
||||
"440524198001010012",
|
||||
"11010119900307765X"
|
||||
]
|
||||
|
||||
invalid_id_cards = [
|
||||
"123456789012345678",
|
||||
"abcdefghij",
|
||||
"11010119900307765"
|
||||
]
|
||||
|
||||
for id_card in valid_id_cards:
|
||||
result = Validator.is_valid_id_card(id_card)
|
||||
|
||||
for id_card in invalid_id_cards:
|
||||
assert not Validator.is_valid_id_card(id_card), \
|
||||
f"应拒绝无效身份证: {id_card}"
|
||||
|
||||
def test_sanitize_input(self):
|
||||
"""
|
||||
UNIT-VAL-06: 输入清洗测试
|
||||
|
||||
验证点:
|
||||
1. 移除危险字符
|
||||
2. 转义特殊字符
|
||||
3. 保留安全内容
|
||||
"""
|
||||
dangerous_inputs = [
|
||||
"<script>alert('xss')</script>",
|
||||
"'; DROP TABLE users; --",
|
||||
"../../../etc/passwd"
|
||||
]
|
||||
|
||||
for dangerous_input in dangerous_inputs:
|
||||
sanitized = Validator.sanitize(dangerous_input)
|
||||
|
||||
assert "<script>" not in sanitized
|
||||
assert "DROP TABLE" not in sanitized.upper() or \
|
||||
sanitized != dangerous_input
|
||||
Reference in New Issue
Block a user