refactor(domain): 将领域模型移动到common模块
重构项目结构,将分散在各模块的领域模型统一移动到manage-common模块 更新相关依赖和引用路径 调整docker-compose配置和测试标记 添加新的Playwright测试配置 优化Dockerfile构建过程
This commit is contained in:
@@ -0,0 +1,91 @@
|
||||
"""
|
||||
测试数据管理器使用示例
|
||||
"""
|
||||
|
||||
import pytest
|
||||
import time
|
||||
from api.user_api import UserAPI
|
||||
from api.role_api import RoleAPI
|
||||
|
||||
|
||||
@pytest.mark.example
|
||||
@pytest.mark.regression
|
||||
class TestDataManagerExample:
|
||||
"""测试数据管理器使用示例"""
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_create_and_cleanup_users(self, authenticated_client, test_data_manager):
|
||||
"""演示测试数据管理器的使用"""
|
||||
user_api = UserAPI(authenticated_client)
|
||||
|
||||
timestamp = int(time.time() * 1000)
|
||||
|
||||
for i in range(3):
|
||||
user_data = {
|
||||
"username": f"managed_user_{timestamp}_{i}",
|
||||
"password": "Test123!@#",
|
||||
"email": f"managed_{timestamp}_{i}@example.com",
|
||||
"status": 1
|
||||
}
|
||||
|
||||
response = await user_api.create_user(user_data)
|
||||
assert response.status_code == 201
|
||||
user_id = response.json()["id"]
|
||||
|
||||
test_data_manager.add_user(user_id)
|
||||
|
||||
cleanup_count = test_data_manager.get_stats()
|
||||
assert cleanup_count["users"] == 3
|
||||
|
||||
all_users = await user_api.get_all_users()
|
||||
assert all_users.status_code == 200
|
||||
|
||||
await test_data_manager.cleanup_all()
|
||||
|
||||
final_count = test_data_manager.get_stats()
|
||||
assert final_count["users"] == 0
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_multiple_resources_cleanup(self, authenticated_client, test_data_manager):
|
||||
"""演示多资源清理"""
|
||||
user_api = UserAPI(authenticated_client)
|
||||
role_api = RoleAPI(authenticated_client)
|
||||
|
||||
timestamp = int(time.time() * 1000)
|
||||
|
||||
role_data = {
|
||||
"roleName": f"Managed_Role_{timestamp}",
|
||||
"roleKey": f"managed_role_{timestamp}",
|
||||
"roleSort": 1,
|
||||
"status": 1
|
||||
}
|
||||
|
||||
role_response = await role_api.create_role(role_data)
|
||||
assert role_response.status_code == 201
|
||||
role_id = role_response.json()["id"]
|
||||
test_data_manager.add_role(role_id)
|
||||
|
||||
for i in range(2):
|
||||
user_data = {
|
||||
"username": f"role_user_{timestamp}_{i}",
|
||||
"password": "Test123!@#",
|
||||
"email": f"role_user_{timestamp}_{i}@example.com",
|
||||
"status": 1
|
||||
}
|
||||
|
||||
user_response = await user_api.create_user(user_data)
|
||||
assert user_response.status_code == 201
|
||||
user_id = user_response.json()["id"]
|
||||
test_data_manager.add_user(user_id)
|
||||
|
||||
await user_api.update_user(user_id, {"roleId": role_id})
|
||||
|
||||
cleanup_count = test_data_manager.get_stats()
|
||||
assert cleanup_count["roles"] == 1
|
||||
assert cleanup_count["users"] == 2
|
||||
|
||||
await test_data_manager.cleanup_all()
|
||||
|
||||
final_count = test_data_manager.get_stats()
|
||||
assert final_count["roles"] == 0
|
||||
assert final_count["users"] == 0
|
||||
@@ -0,0 +1,311 @@
|
||||
"""
|
||||
端到端业务流程测试用例
|
||||
"""
|
||||
|
||||
import pytest
|
||||
import time
|
||||
from api.auth_api import AuthAPI
|
||||
from api.user_api import UserAPI
|
||||
from api.role_api import RoleAPI
|
||||
from api.notice_api import SysNoticeAPI
|
||||
|
||||
|
||||
@pytest.mark.e2e
|
||||
@pytest.mark.regression
|
||||
class TestBusinessFlow:
|
||||
"""端到端业务流程测试类"""
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_complete_user_lifecycle(self, authenticated_client):
|
||||
"""测试完整用户生命周期"""
|
||||
auth_api = AuthAPI(authenticated_client)
|
||||
user_api = UserAPI(authenticated_client)
|
||||
|
||||
timestamp = int(time.time() * 1000)
|
||||
|
||||
new_user_data = {
|
||||
"username": f"e2e_user_{timestamp}",
|
||||
"password": "Test123!@#",
|
||||
"email": f"e2e_{timestamp}@example.com",
|
||||
"phone": "13800138000",
|
||||
"status": 1
|
||||
}
|
||||
|
||||
create_response = await user_api.create_user(new_user_data)
|
||||
assert create_response.status_code == 201
|
||||
user_id = create_response.json()["id"]
|
||||
|
||||
get_response = await user_api.get_user_by_id(user_id)
|
||||
assert get_response.status_code == 200
|
||||
user_data = get_response.json()
|
||||
assert user_data["username"] == new_user_data["username"]
|
||||
|
||||
update_data = {"email": f"updated_{timestamp}@example.com"}
|
||||
update_response = await user_api.update_user(user_id, update_data)
|
||||
assert update_response.status_code == 200
|
||||
|
||||
delete_response = await user_api.delete_user(user_id)
|
||||
assert delete_response.status_code in [200, 204]
|
||||
|
||||
final_get_response = await user_api.get_user_by_id(user_id)
|
||||
assert final_get_response.status_code == 404
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_role_assignment_workflow(self, authenticated_client):
|
||||
"""测试角色分配工作流"""
|
||||
user_api = UserAPI(authenticated_client)
|
||||
role_api = RoleAPI(authenticated_client)
|
||||
|
||||
timestamp = int(time.time() * 1000)
|
||||
|
||||
role_data = {
|
||||
"roleName": f"E2E_Role_{timestamp}",
|
||||
"roleKey": f"e2e_role_{timestamp}",
|
||||
"roleSort": 1,
|
||||
"status": 1
|
||||
}
|
||||
|
||||
role_response = await role_api.create_role(role_data)
|
||||
assert role_response.status_code == 201
|
||||
role_id = role_response.json()["id"]
|
||||
|
||||
user_data = {
|
||||
"username": f"e2e_user_{timestamp}",
|
||||
"password": "Test123!@#",
|
||||
"email": f"e2e_{timestamp}@example.com",
|
||||
"status": 1
|
||||
}
|
||||
|
||||
user_response = await user_api.create_user(user_data)
|
||||
assert user_response.status_code == 201
|
||||
user_id = user_response.json()["id"]
|
||||
|
||||
assign_response = await user_api.update_user(user_id, {"roleId": role_id})
|
||||
assert assign_response.status_code == 200
|
||||
|
||||
verify_response = await user_api.get_user_by_id(user_id)
|
||||
assert verify_response.json()["roleId"] == role_id
|
||||
|
||||
await user_api.delete_user(user_id)
|
||||
await role_api.delete_role(role_id)
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_notification_workflow(self, authenticated_client):
|
||||
"""测试通知工作流"""
|
||||
notice_api = SysNoticeAPI(authenticated_client)
|
||||
user_api = UserAPI(authenticated_client)
|
||||
|
||||
timestamp = int(time.time() * 1000)
|
||||
|
||||
notice_data = {
|
||||
"noticeTitle": f"E2E_Notice_{timestamp}",
|
||||
"noticeType": "1",
|
||||
"noticeContent": "This is an E2E test notice",
|
||||
"status": "0"
|
||||
}
|
||||
|
||||
create_response = await notice_api.create(notice_data)
|
||||
assert create_response.status_code == 201
|
||||
notice_data_response = create_response.json()
|
||||
|
||||
notice_id = notice_data_response.get("id")
|
||||
if not notice_id:
|
||||
notice_title = notice_data_response.get("noticeTitle")
|
||||
all_notices = await notice_api.get_all()
|
||||
notices = all_notices.json()
|
||||
notice = next((n for n in notices if n["noticeTitle"] == notice_title), None)
|
||||
notice_id = notice["id"] if notice else None
|
||||
|
||||
assert notice_id is not None
|
||||
|
||||
get_response = await notice_api.get_by_id(notice_id)
|
||||
assert get_response.status_code == 200
|
||||
|
||||
all_notices = await notice_api.get_all()
|
||||
assert all_notices.status_code == 200
|
||||
notices = all_notices.json()
|
||||
assert any(notice["id"] == notice_id for notice in notices)
|
||||
|
||||
update_data = {"noticeTitle": f"Updated_Notice_{timestamp}"}
|
||||
update_response = await notice_api.update(notice_id, update_data)
|
||||
assert update_response.status_code == 200
|
||||
|
||||
await notice_api.delete(notice_id)
|
||||
|
||||
final_get = await notice_api.get_by_id(notice_id)
|
||||
assert final_get.status_code == 404
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_multi_role_user_management(self, authenticated_client):
|
||||
"""测试多角色用户管理"""
|
||||
user_api = UserAPI(authenticated_client)
|
||||
role_api = RoleAPI(authenticated_client)
|
||||
|
||||
timestamp = int(time.time() * 1000)
|
||||
|
||||
admin_role_data = {
|
||||
"roleName": f"Admin_{timestamp}",
|
||||
"roleKey": f"admin_{timestamp}",
|
||||
"roleSort": 1,
|
||||
"status": 1
|
||||
}
|
||||
admin_role = await role_api.create_role(admin_role_data)
|
||||
admin_role_id = admin_role.json()["id"]
|
||||
|
||||
user_role_data = {
|
||||
"roleName": f"User_{timestamp}",
|
||||
"roleKey": f"user_{timestamp}",
|
||||
"roleSort": 2,
|
||||
"status": 1
|
||||
}
|
||||
user_role = await role_api.create_role(user_role_data)
|
||||
user_role_id = user_role.json()["id"]
|
||||
|
||||
admin_user_data = {
|
||||
"username": f"admin_{timestamp}",
|
||||
"password": "Admin123!@#",
|
||||
"email": f"admin_{timestamp}@example.com",
|
||||
"status": 1
|
||||
}
|
||||
admin_user = await user_api.create_user(admin_user_data)
|
||||
admin_user_id = admin_user.json()["id"]
|
||||
|
||||
regular_user_data = {
|
||||
"username": f"regular_{timestamp}",
|
||||
"password": "User123!@#",
|
||||
"email": f"regular_{timestamp}@example.com",
|
||||
"status": 1
|
||||
}
|
||||
regular_user = await user_api.create_user(regular_user_data)
|
||||
regular_user_id = regular_user.json()["id"]
|
||||
|
||||
await user_api.update_user(admin_user_id, {"roleId": admin_role_id})
|
||||
await user_api.update_user(regular_user_id, {"roleId": user_role_id})
|
||||
|
||||
admin_verify = await user_api.get_user_by_id(admin_user_id)
|
||||
assert admin_verify.json()["roleId"] == admin_role_id
|
||||
|
||||
regular_verify = await user_api.get_user_by_id(regular_user_id)
|
||||
assert regular_verify.json()["roleId"] == user_role_id
|
||||
|
||||
all_users = await user_api.get_all_users()
|
||||
users = all_users.json()
|
||||
assert len(users) >= 2
|
||||
|
||||
await user_api.delete_user(admin_user_id)
|
||||
await user_api.delete_user(regular_user_id)
|
||||
await role_api.delete_role(admin_role_id)
|
||||
await role_api.delete_role(user_role_id)
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_user_role_cascade_operations(self, authenticated_client):
|
||||
"""测试用户角色级联操作"""
|
||||
user_api = UserAPI(authenticated_client)
|
||||
role_api = RoleAPI(authenticated_client)
|
||||
|
||||
timestamp = int(time.time() * 1000)
|
||||
|
||||
role_data = {
|
||||
"roleName": f"Cascade_Role_{timestamp}",
|
||||
"roleKey": f"cascade_role_{timestamp}",
|
||||
"roleSort": 1,
|
||||
"status": 1
|
||||
}
|
||||
role_response = await role_api.create_role(role_data)
|
||||
role_id = role_response.json()["id"]
|
||||
|
||||
user_ids = []
|
||||
for i in range(3):
|
||||
user_data = {
|
||||
"username": f"cascade_user_{timestamp}_{i}",
|
||||
"password": "Test123!@#",
|
||||
"email": f"cascade_{timestamp}_{i}@example.com",
|
||||
"status": 1
|
||||
}
|
||||
user_response = await user_api.create_user(user_data)
|
||||
user_id = user_response.json()["id"]
|
||||
user_ids.append(user_id)
|
||||
await user_api.update_user(user_id, {"roleId": role_id})
|
||||
|
||||
await role_api.update_role(role_id, {"status": 0})
|
||||
|
||||
for user_id in user_ids:
|
||||
user_data = await user_api.get_user_by_id(user_id)
|
||||
assert user_data.json()["roleId"] == role_id
|
||||
|
||||
for user_id in user_ids:
|
||||
await user_api.delete_user(user_id)
|
||||
await role_api.delete_role(role_id)
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_search_and_filter_workflow(self, authenticated_client):
|
||||
"""测试搜索和过滤工作流"""
|
||||
user_api = UserAPI(authenticated_client)
|
||||
role_api = RoleAPI(authenticated_client)
|
||||
|
||||
timestamp = int(time.time() * 1000)
|
||||
|
||||
role_data = {
|
||||
"roleName": f"Search_Role_{timestamp}",
|
||||
"roleKey": f"search_role_{timestamp}",
|
||||
"roleSort": 1,
|
||||
"status": 1
|
||||
}
|
||||
role_response = await role_api.create_role(role_data)
|
||||
role_id = role_response.json()["id"]
|
||||
|
||||
user_ids = []
|
||||
for i in range(5):
|
||||
user_data = {
|
||||
"username": f"search_{timestamp}_{i}",
|
||||
"password": "Test123!@#",
|
||||
"email": f"search_{timestamp}_{i}@example.com",
|
||||
"status": 1
|
||||
}
|
||||
user_response = await user_api.create_user(user_data)
|
||||
user_id = user_response.json()["id"]
|
||||
user_ids.append(user_id)
|
||||
|
||||
search_response = await user_api.get_users_by_page(keyword=f"search_{timestamp}")
|
||||
assert search_response.status_code == 200
|
||||
search_data = search_response.json()
|
||||
assert len(search_data["content"]) >= 5
|
||||
|
||||
all_users = await user_api.get_all_users()
|
||||
assert all_users.status_code == 200
|
||||
|
||||
for user_id in user_ids:
|
||||
await user_api.delete_user(user_id)
|
||||
await role_api.delete_role(role_id)
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_error_recovery_workflow(self, authenticated_client):
|
||||
"""测试错误恢复工作流"""
|
||||
user_api = UserAPI(authenticated_client)
|
||||
|
||||
timestamp = int(time.time() * 1000)
|
||||
|
||||
invalid_user_data = {
|
||||
"username": "",
|
||||
"password": "123",
|
||||
"email": "invalid-email"
|
||||
}
|
||||
|
||||
invalid_response = await user_api.create_user(invalid_user_data)
|
||||
assert invalid_response.status_code in [400, 409, 422]
|
||||
|
||||
valid_user_data = {
|
||||
"username": f"recovery_{timestamp}",
|
||||
"password": "Valid123!@#",
|
||||
"email": f"recovery_{timestamp}@example.com",
|
||||
"status": 1
|
||||
}
|
||||
|
||||
valid_response = await user_api.create_user(valid_user_data)
|
||||
assert valid_response.status_code == 201
|
||||
user_id = valid_response.json()["id"]
|
||||
|
||||
get_response = await user_api.get_user_by_id(user_id)
|
||||
assert get_response.status_code == 200
|
||||
|
||||
await user_api.delete_user(user_id)
|
||||
@@ -0,0 +1,331 @@
|
||||
"""
|
||||
异常场景测试用例
|
||||
"""
|
||||
|
||||
import pytest
|
||||
import time
|
||||
from api.user_api import UserAPI
|
||||
from api.role_api import RoleAPI
|
||||
from api.notice_api import SysNoticeAPI
|
||||
|
||||
|
||||
@pytest.mark.exception
|
||||
@pytest.mark.regression
|
||||
class TestExceptionScenarios:
|
||||
"""异常场景测试类"""
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_create_user_with_duplicate_username(self, authenticated_client, test_user_data, cleanup_user):
|
||||
"""测试创建重复用户名的用户"""
|
||||
user_api = UserAPI(authenticated_client)
|
||||
|
||||
create_response = await user_api.create_user(test_user_data)
|
||||
assert create_response.status_code == 201
|
||||
user_id = create_response.json()["id"]
|
||||
cleanup_user.append(user_id)
|
||||
|
||||
duplicate_response = await user_api.create_user(test_user_data)
|
||||
assert duplicate_response.status_code in [400, 409]
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_create_user_with_invalid_email(self, authenticated_client):
|
||||
"""测试创建邮箱格式无效的用户"""
|
||||
user_api = UserAPI(authenticated_client)
|
||||
|
||||
invalid_emails = [
|
||||
"invalid-email",
|
||||
"@example.com",
|
||||
"user@",
|
||||
"user@domain",
|
||||
"user name@example.com"
|
||||
]
|
||||
|
||||
for invalid_email in invalid_emails:
|
||||
timestamp = int(time.time() * 1000)
|
||||
user_data = {
|
||||
"username": f"test_{timestamp}",
|
||||
"password": "Test123!@#",
|
||||
"email": invalid_email,
|
||||
"status": 1
|
||||
}
|
||||
|
||||
response = await user_api.create_user(user_data)
|
||||
assert response.status_code in [400, 422]
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_create_user_with_weak_password(self, authenticated_client):
|
||||
"""测试创建弱密码用户"""
|
||||
user_api = UserAPI(authenticated_client)
|
||||
|
||||
weak_passwords = [
|
||||
"123456",
|
||||
"password",
|
||||
"qwerty",
|
||||
"111111",
|
||||
"abc123"
|
||||
]
|
||||
|
||||
for weak_password in weak_passwords:
|
||||
timestamp = int(time.time() * 1000)
|
||||
user_data = {
|
||||
"username": f"test_{timestamp}",
|
||||
"password": weak_password,
|
||||
"email": f"test_{timestamp}@example.com",
|
||||
"status": 1
|
||||
}
|
||||
|
||||
response = await user_api.create_user(user_data)
|
||||
assert response.status_code in [400, 422]
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_create_user_with_missing_fields(self, authenticated_client):
|
||||
"""测试创建缺少必填字段的用户"""
|
||||
user_api = UserAPI(authenticated_client)
|
||||
|
||||
missing_field_scenarios = [
|
||||
{"password": "Test123!@#", "email": "test@example.com"},
|
||||
{"username": "testuser", "email": "test@example.com"},
|
||||
{"username": "testuser", "password": "Test123!@#"}
|
||||
]
|
||||
|
||||
for scenario in missing_field_scenarios:
|
||||
response = await user_api.create_user(scenario)
|
||||
assert response.status_code in [400, 422]
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_update_nonexistent_user(self, authenticated_client):
|
||||
"""测试更新不存在的用户"""
|
||||
user_api = UserAPI(authenticated_client)
|
||||
|
||||
update_data = {"email": "updated@example.com"}
|
||||
response = await user_api.update_user(999999, update_data)
|
||||
assert response.status_code == 404
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_delete_nonexistent_user(self, authenticated_client):
|
||||
"""测试删除不存在的用户"""
|
||||
user_api = UserAPI(authenticated_client)
|
||||
|
||||
response = await user_api.delete_user(999999)
|
||||
assert response.status_code == 404
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_create_role_with_duplicate_key(self, authenticated_client, test_role_data, cleanup_role):
|
||||
"""测试创建重复角色键的角色"""
|
||||
role_api = RoleAPI(authenticated_client)
|
||||
|
||||
create_response = await role_api.create_role(test_role_data)
|
||||
assert create_response.status_code == 201
|
||||
role_id = create_response.json()["id"]
|
||||
cleanup_role.append(role_id)
|
||||
|
||||
duplicate_response = await role_api.create_role(test_role_data)
|
||||
assert duplicate_response.status_code in [400, 409]
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_create_role_with_invalid_status(self, authenticated_client):
|
||||
"""测试创建状态无效的角色"""
|
||||
role_api = RoleAPI(authenticated_client)
|
||||
|
||||
invalid_statuses = ["2", "3", "invalid", "true", "false"]
|
||||
|
||||
for invalid_status in invalid_statuses:
|
||||
timestamp = int(time.time() * 1000)
|
||||
role_data = {
|
||||
"roleName": f"TestRole_{timestamp}",
|
||||
"roleKey": f"test_role_{timestamp}",
|
||||
"roleSort": 1,
|
||||
"status": invalid_status
|
||||
}
|
||||
|
||||
response = await role_api.create_role(role_data)
|
||||
assert response.status_code in [400, 422]
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_update_nonexistent_role(self, authenticated_client):
|
||||
"""测试更新不存在的角色"""
|
||||
role_api = RoleAPI(authenticated_client)
|
||||
|
||||
update_data = {"roleName": "Updated Role"}
|
||||
response = await role_api.update_role(999999, update_data)
|
||||
assert response.status_code == 404
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_create_notice_with_invalid_type(self, authenticated_client):
|
||||
"""测试创建类型无效的公告"""
|
||||
notice_api = SysNoticeAPI(authenticated_client)
|
||||
|
||||
invalid_types = ["3", "4", "invalid", "true", "false"]
|
||||
|
||||
for invalid_type in invalid_types:
|
||||
timestamp = int(time.time() * 1000)
|
||||
notice_data = {
|
||||
"noticeTitle": f"TestNotice_{timestamp}",
|
||||
"noticeType": invalid_type,
|
||||
"noticeContent": "Test content",
|
||||
"status": "0"
|
||||
}
|
||||
|
||||
response = await notice_api.create(notice_data)
|
||||
assert response.status_code in [400, 422]
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_create_notice_with_empty_content(self, authenticated_client):
|
||||
"""测试创建内容为空的公告"""
|
||||
notice_api = SysNoticeAPI(authenticated_client)
|
||||
|
||||
empty_content_scenarios = [
|
||||
{"noticeTitle": "Test", "noticeType": "1", "noticeContent": "", "status": "0"},
|
||||
{"noticeTitle": "", "noticeType": "1", "noticeContent": "Test", "status": "0"},
|
||||
{"noticeTitle": "Test", "noticeType": "1", "noticeContent": " ", "status": "0"}
|
||||
]
|
||||
|
||||
for scenario in empty_content_scenarios:
|
||||
response = await notice_api.create(scenario)
|
||||
assert response.status_code in [400, 422]
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_update_nonexistent_notice(self, authenticated_client):
|
||||
"""测试更新不存在的公告"""
|
||||
notice_api = SysNoticeAPI(authenticated_client)
|
||||
|
||||
update_data = {"noticeTitle": "Updated Notice"}
|
||||
response = await notice_api.update(999999, update_data)
|
||||
assert response.status_code == 404
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_delete_nonexistent_notice(self, authenticated_client):
|
||||
"""测试删除不存在的公告"""
|
||||
notice_api = SysNoticeAPI(authenticated_client)
|
||||
|
||||
response = await notice_api.delete(999999)
|
||||
assert response.status_code == 404
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_user_with_invalid_id(self, authenticated_client):
|
||||
"""测试获取ID无效的用户"""
|
||||
user_api = UserAPI(authenticated_client)
|
||||
|
||||
invalid_ids = [-1, 0, "abc", "1.5", "999999999999"]
|
||||
|
||||
for invalid_id in invalid_ids:
|
||||
try:
|
||||
response = await user_api.get_user_by_id(int(invalid_id) if isinstance(invalid_id, (int, str)) else invalid_id)
|
||||
assert response.status_code in [400, 404]
|
||||
except (ValueError, TypeError):
|
||||
pass
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_pagination_with_invalid_params(self, authenticated_client):
|
||||
"""测试分页参数无效的查询"""
|
||||
user_api = UserAPI(authenticated_client)
|
||||
|
||||
invalid_params = [
|
||||
{"page": -1, "size": 10},
|
||||
{"page": 0, "size": -1},
|
||||
{"page": 0, "size": 0},
|
||||
{"page": 0, "size": 10000},
|
||||
{"page": "abc", "size": 10},
|
||||
{"page": 0, "size": "abc"}
|
||||
]
|
||||
|
||||
for params in invalid_params:
|
||||
try:
|
||||
response = await user_api.get_users_by_page(**params)
|
||||
assert response.status_code in [400, 422]
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_search_with_special_characters(self, authenticated_client):
|
||||
"""测试搜索特殊字符"""
|
||||
user_api = UserAPI(authenticated_client)
|
||||
|
||||
special_chars = [
|
||||
"<script>alert('xss')</script>",
|
||||
"'; DROP TABLE users; --",
|
||||
"../../../etc/passwd",
|
||||
"{{7*7}}",
|
||||
"%00%00%00%00"
|
||||
]
|
||||
|
||||
for search_term in special_chars:
|
||||
response = await user_api.get_users_by_page(keyword=search_term)
|
||||
assert response.status_code in [200, 400]
|
||||
|
||||
if response.status_code == 200:
|
||||
data = response.json()
|
||||
assert "content" in data
|
||||
for user in data["content"]:
|
||||
assert search_term.lower() not in str(user).lower()
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_concurrent_same_resource_update(self, authenticated_client, test_user_data, cleanup_user):
|
||||
"""测试并发更新同一资源"""
|
||||
user_api = UserAPI(authenticated_client)
|
||||
|
||||
create_response = await user_api.create_user(test_user_data)
|
||||
assert create_response.status_code == 201
|
||||
user_id = create_response.json()["id"]
|
||||
cleanup_user.append(user_id)
|
||||
|
||||
import asyncio
|
||||
update_tasks = [
|
||||
user_api.update_user(user_id, {"email": f"concurrent1_{time.time()}@example.com"}),
|
||||
user_api.update_user(user_id, {"email": f"concurrent2_{time.time()}@example.com"}),
|
||||
user_api.update_user(user_id, {"email": f"concurrent3_{time.time()}@example.com"})
|
||||
]
|
||||
|
||||
results = await asyncio.gather(*update_tasks, return_exceptions=True)
|
||||
|
||||
successful_updates = sum(1 for r in results if r.status_code == 200)
|
||||
assert successful_updates >= 1, "至少应该有一个更新成功"
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_large_payload_handling(self, authenticated_client):
|
||||
"""测试大数据负载处理"""
|
||||
user_api = UserAPI(authenticated_client)
|
||||
|
||||
large_content = "x" * 10000
|
||||
user_data = {
|
||||
"username": f"large_payload_{int(time.time() * 1000)}",
|
||||
"password": "Test123!@#",
|
||||
"email": f"large_{int(time.time() * 1000)}@example.com",
|
||||
"phone": large_content
|
||||
}
|
||||
|
||||
response = await user_api.create_user(user_data)
|
||||
assert response.status_code in [201, 400, 413]
|
||||
|
||||
if response.status_code in [400, 413]:
|
||||
logger.info("系统正确拒绝了过大的负载")
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_unauthorized_access(self, http_client):
|
||||
"""测试未授权访问"""
|
||||
user_api = UserAPI(http_client)
|
||||
|
||||
response = await user_api.get_all_users()
|
||||
assert response.status_code == 401
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_rate_limiting(self, authenticated_client):
|
||||
"""测试速率限制"""
|
||||
user_api = UserAPI(authenticated_client)
|
||||
|
||||
requests_made = 0
|
||||
rate_limit_hit = False
|
||||
|
||||
for i in range(100):
|
||||
response = await user_api.get_all_users()
|
||||
requests_made += 1
|
||||
|
||||
if response.status_code == 429:
|
||||
rate_limit_hit = True
|
||||
logger.info(f"速率限制在第 {requests_made} 个请求时触发")
|
||||
break
|
||||
|
||||
if rate_limit_hit:
|
||||
logger.info("系统正确实施了速率限制")
|
||||
else:
|
||||
logger.info("未触发速率限制(可能未配置或阈值较高)")
|
||||
@@ -0,0 +1,242 @@
|
||||
"""
|
||||
菜单管理测试用例
|
||||
"""
|
||||
|
||||
import pytest
|
||||
import time
|
||||
from api.menu_api import MenuAPI
|
||||
|
||||
|
||||
@pytest.mark.menu
|
||||
@pytest.mark.regression
|
||||
class TestMenu:
|
||||
"""菜单管理测试类"""
|
||||
|
||||
@pytest.fixture
|
||||
def test_menu_data(self):
|
||||
"""测试菜单数据"""
|
||||
timestamp = int(time.time() * 1000)
|
||||
return {
|
||||
"menuName": f"测试菜单_{timestamp}",
|
||||
"parentId": 0,
|
||||
"orderNum": 1,
|
||||
"menuType": "C",
|
||||
"perms": f"system:menu:{timestamp}",
|
||||
"component": f"menu/component/{timestamp}",
|
||||
"status": "0"
|
||||
}
|
||||
|
||||
@pytest.fixture
|
||||
async def cleanup_menu(self, authenticated_client):
|
||||
"""清理测试菜单"""
|
||||
menu_ids = []
|
||||
|
||||
yield menu_ids
|
||||
|
||||
for menu_id in menu_ids:
|
||||
try:
|
||||
await authenticated_client.delete(f"/api/menus/{menu_id}")
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_create_menu_success(self, authenticated_client, test_menu_data, cleanup_menu):
|
||||
"""测试创建菜单成功"""
|
||||
menu_api = MenuAPI(authenticated_client)
|
||||
response = await menu_api.create_menu(test_menu_data)
|
||||
|
||||
assert response.status_code == 201
|
||||
data = response.json()
|
||||
assert "id" in data
|
||||
assert data["menuName"] == test_menu_data["menuName"]
|
||||
assert data["parentId"] == test_menu_data["parentId"]
|
||||
assert data["menuType"] == test_menu_data["menuType"]
|
||||
|
||||
cleanup_menu.append(data["id"])
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_menu_by_id_success(self, authenticated_client, test_menu_data, cleanup_menu):
|
||||
"""测试根据ID获取菜单成功"""
|
||||
menu_api = MenuAPI(authenticated_client)
|
||||
|
||||
create_response = await menu_api.create_menu(test_menu_data)
|
||||
menu_id = create_response.json()["id"]
|
||||
|
||||
response = await menu_api.get_menu_by_id(menu_id)
|
||||
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["id"] == menu_id
|
||||
assert data["menuName"] == test_menu_data["menuName"]
|
||||
|
||||
cleanup_menu.append(menu_id)
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_menu_by_id_not_found(self, authenticated_client):
|
||||
"""测试获取不存在的菜单"""
|
||||
menu_api = MenuAPI(authenticated_client)
|
||||
response = await menu_api.get_menu_by_id(999999)
|
||||
|
||||
assert response.status_code == 404
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_all_menus_success(self, authenticated_client):
|
||||
"""测试获取所有菜单成功"""
|
||||
menu_api = MenuAPI(authenticated_client)
|
||||
response = await menu_api.get_all_menus()
|
||||
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert isinstance(data, list)
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_menu_tree_success(self, authenticated_client):
|
||||
"""测试获取菜单树成功"""
|
||||
menu_api = MenuAPI(authenticated_client)
|
||||
response = await menu_api.get_menu_tree()
|
||||
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert isinstance(data, list)
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_update_menu_success(self, authenticated_client, test_menu_data, cleanup_menu):
|
||||
"""测试更新菜单成功"""
|
||||
menu_api = MenuAPI(authenticated_client)
|
||||
|
||||
create_response = await menu_api.create_menu(test_menu_data)
|
||||
menu_id = create_response.json()["id"]
|
||||
|
||||
timestamp = int(time.time() * 1000)
|
||||
update_data = {
|
||||
"menuName": f"更新后菜单_{timestamp}",
|
||||
"orderNum": 2
|
||||
}
|
||||
response = await menu_api.update_menu(menu_id, update_data)
|
||||
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["menuName"] == f"更新后菜单_{timestamp}"
|
||||
assert data["orderNum"] == 2
|
||||
|
||||
cleanup_menu.append(menu_id)
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_delete_menu_success(self, authenticated_client, test_menu_data, cleanup_menu):
|
||||
"""测试删除菜单成功"""
|
||||
menu_api = MenuAPI(authenticated_client)
|
||||
|
||||
create_response = await menu_api.create_menu(test_menu_data)
|
||||
menu_id = create_response.json()["id"]
|
||||
|
||||
response = await menu_api.delete_menu(menu_id)
|
||||
|
||||
assert response.status_code == 204
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_menus_by_parent_success(self, authenticated_client, test_menu_data, cleanup_menu):
|
||||
"""测试根据父菜单ID获取子菜单成功"""
|
||||
menu_api = MenuAPI(authenticated_client)
|
||||
|
||||
parent_response = await menu_api.create_menu(test_menu_data)
|
||||
parent_id = parent_response.json()["id"]
|
||||
|
||||
timestamp = int(time.time() * 1000)
|
||||
child_menu_data = test_menu_data.copy()
|
||||
child_menu_data["menuName"] = f"子菜单_{timestamp}"
|
||||
child_menu_data["parentId"] = parent_id
|
||||
|
||||
child_response = await menu_api.create_menu(child_menu_data)
|
||||
child_id = child_response.json()["id"]
|
||||
|
||||
response = await menu_api.get_menus_by_parent(parent_id)
|
||||
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert isinstance(data, list)
|
||||
assert any(menu["id"] == child_id for menu in data)
|
||||
|
||||
cleanup_menu.extend([parent_id, child_id])
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_menus_by_type_success(self, authenticated_client, test_menu_data, cleanup_menu):
|
||||
"""测试根据菜单类型获取菜单成功"""
|
||||
menu_api = MenuAPI(authenticated_client)
|
||||
|
||||
create_response = await menu_api.create_menu(test_menu_data)
|
||||
menu_id = create_response.json()["id"]
|
||||
|
||||
response = await menu_api.get_menus_by_type("C")
|
||||
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert isinstance(data, list)
|
||||
assert any(menu["id"] == menu_id for menu in data)
|
||||
|
||||
cleanup_menu.append(menu_id)
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_create_menu_with_parent_success(self, authenticated_client, test_menu_data, cleanup_menu):
|
||||
"""测试创建带父菜单的子菜单成功"""
|
||||
menu_api = MenuAPI(authenticated_client)
|
||||
|
||||
parent_response = await menu_api.create_menu(test_menu_data)
|
||||
parent_id = parent_response.json()["id"]
|
||||
|
||||
timestamp = int(time.time() * 1000)
|
||||
child_menu_data = test_menu_data.copy()
|
||||
child_menu_data["menuName"] = f"子菜单_{timestamp}"
|
||||
child_menu_data["parentId"] = parent_id
|
||||
|
||||
response = await menu_api.create_menu(child_menu_data)
|
||||
|
||||
assert response.status_code == 201
|
||||
data = response.json()
|
||||
assert data["parentId"] == parent_id
|
||||
|
||||
cleanup_menu.extend([parent_id, data["id"]])
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_create_menu_directory_type_success(self, authenticated_client, cleanup_menu):
|
||||
"""测试创建目录类型菜单成功"""
|
||||
menu_api = MenuAPI(authenticated_client)
|
||||
timestamp = int(time.time() * 1000)
|
||||
|
||||
menu_data = {
|
||||
"menuName": f"目录_{timestamp}",
|
||||
"parentId": 0,
|
||||
"orderNum": 1,
|
||||
"menuType": "M",
|
||||
"status": "0"
|
||||
}
|
||||
|
||||
response = await menu_api.create_menu(menu_data)
|
||||
|
||||
assert response.status_code == 201
|
||||
data = response.json()
|
||||
assert data["menuType"] == "M"
|
||||
|
||||
cleanup_menu.append(data["id"])
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_create_menu_button_type_success(self, authenticated_client, cleanup_menu):
|
||||
"""测试创建按钮类型菜单成功"""
|
||||
menu_api = MenuAPI(authenticated_client)
|
||||
timestamp = int(time.time() * 1000)
|
||||
|
||||
menu_data = {
|
||||
"menuName": f"按钮_{timestamp}",
|
||||
"parentId": 1,
|
||||
"orderNum": 1,
|
||||
"menuType": "F",
|
||||
"perms": f"system:button:{timestamp}",
|
||||
"status": "0"
|
||||
}
|
||||
|
||||
response = await menu_api.create_menu(menu_data)
|
||||
|
||||
assert response.status_code == 201
|
||||
data = response.json()
|
||||
assert data["menuType"] == "F"
|
||||
|
||||
cleanup_menu.append(data["id"])
|
||||
@@ -1,127 +0,0 @@
|
||||
"""
|
||||
OAuth2客户端管理测试用例
|
||||
"""
|
||||
|
||||
import pytest
|
||||
from httpx import AsyncClient
|
||||
|
||||
|
||||
@pytest.mark.oauth2
|
||||
@pytest.mark.regression
|
||||
class TestOAuth2:
|
||||
"""OAuth2客户端管理测试类"""
|
||||
|
||||
@pytest.fixture
|
||||
def test_oauth2_client_data(self):
|
||||
"""测试OAuth2客户端数据"""
|
||||
import time
|
||||
timestamp = int(time.time() * 1000)
|
||||
return {
|
||||
"clientId": f"test-client-{timestamp}",
|
||||
"clientSecret": "secret123",
|
||||
"clientName": "Test Client",
|
||||
"webServerRedirectUri": "http://localhost:8080/callback",
|
||||
"scope": "read,write",
|
||||
"authorizedGrantTypes": "authorization_code,refresh_token",
|
||||
"accessTokenValiditySeconds": 7200,
|
||||
"refreshTokenValiditySeconds": 2592000,
|
||||
"autoApprove": False,
|
||||
"enabled": True
|
||||
}
|
||||
|
||||
@pytest.fixture
|
||||
async def cleanup_oauth2_client(self, authenticated_client: AsyncClient):
|
||||
"""清理测试OAuth2客户端"""
|
||||
client_ids = []
|
||||
|
||||
yield client_ids
|
||||
|
||||
for client_id in client_ids:
|
||||
try:
|
||||
await authenticated_client.delete(f"/api/oauth2/clients/{client_id}")
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_create_oauth2_client_success(self, authenticated_client, test_oauth2_client_data, cleanup_oauth2_client):
|
||||
"""测试创建OAuth2客户端成功"""
|
||||
response = await authenticated_client.post("/api/oauth2/clients", json=test_oauth2_client_data)
|
||||
|
||||
assert response.status_code == 201
|
||||
data = response.json()
|
||||
assert "id" in data
|
||||
assert data["clientId"] == test_oauth2_client_data["clientId"]
|
||||
assert data["clientName"] == test_oauth2_client_data["clientName"]
|
||||
assert "clientSecret" not in data or data["clientSecret"] != test_oauth2_client_data["clientSecret"]
|
||||
|
||||
cleanup_oauth2_client.append(data["id"])
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_oauth2_client_by_id_success(self, authenticated_client, test_oauth2_client_data, cleanup_oauth2_client):
|
||||
"""测试根据ID获取OAuth2客户端成功"""
|
||||
create_response = await authenticated_client.post("/api/oauth2/clients", json=test_oauth2_client_data)
|
||||
client_id = create_response.json()["id"]
|
||||
|
||||
response = await authenticated_client.get(f"/api/oauth2/clients/{client_id}")
|
||||
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["id"] == client_id
|
||||
assert data["clientId"] == test_oauth2_client_data["clientId"]
|
||||
|
||||
cleanup_oauth2_client.append(client_id)
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_oauth2_client_by_id_not_found(self, authenticated_client):
|
||||
"""测试获取不存在的OAuth2客户端"""
|
||||
response = await authenticated_client.get("/api/oauth2/clients/999999")
|
||||
|
||||
assert response.status_code == 404
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_oauth2_client_by_client_id_success(self, authenticated_client, test_oauth2_client_data, cleanup_oauth2_client):
|
||||
"""测试根据clientId获取OAuth2客户端成功"""
|
||||
create_response = await authenticated_client.post("/api/oauth2/clients", json=test_oauth2_client_data)
|
||||
client_id = create_response.json()["id"]
|
||||
|
||||
response = await authenticated_client.get(f"/api/oauth2/clients/client-id/{test_oauth2_client_data['clientId']}")
|
||||
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["clientId"] == test_oauth2_client_data["clientId"]
|
||||
|
||||
cleanup_oauth2_client.append(client_id)
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_all_oauth2_clients_success(self, authenticated_client):
|
||||
"""测试获取所有OAuth2客户端成功"""
|
||||
response = await authenticated_client.get("/api/oauth2/clients")
|
||||
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert isinstance(data, list)
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_update_oauth2_client_success(self, authenticated_client, test_oauth2_client_data, cleanup_oauth2_client):
|
||||
"""测试更新OAuth2客户端成功"""
|
||||
create_response = await authenticated_client.post("/api/oauth2/clients", json=test_oauth2_client_data)
|
||||
client_id = create_response.json()["id"]
|
||||
|
||||
update_data = {"clientName": "Updated Client Name"}
|
||||
response = await authenticated_client.put(f"/api/oauth2/clients/{client_id}", json=update_data)
|
||||
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["clientName"] == "Updated Client Name"
|
||||
|
||||
cleanup_oauth2_client.append(client_id)
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_delete_oauth2_client_success(self, authenticated_client, test_oauth2_client_data, cleanup_oauth2_client):
|
||||
"""测试删除OAuth2客户端成功"""
|
||||
create_response = await authenticated_client.post("/api/oauth2/clients", json=test_oauth2_client_data)
|
||||
client_id = create_response.json()["id"]
|
||||
|
||||
response = await authenticated_client.delete(f"/api/oauth2/clients/{client_id}")
|
||||
|
||||
assert response.status_code == 204
|
||||
@@ -0,0 +1,200 @@
|
||||
"""
|
||||
性能测试基础框架
|
||||
"""
|
||||
|
||||
import pytest
|
||||
import time
|
||||
import asyncio
|
||||
import statistics
|
||||
from typing import List, Dict, Any
|
||||
from httpx import AsyncClient
|
||||
from loguru import logger
|
||||
|
||||
|
||||
@pytest.mark.performance
|
||||
@pytest.mark.slow
|
||||
class PerformanceTest:
|
||||
"""性能测试基类"""
|
||||
|
||||
@pytest.fixture
|
||||
async def perf_client(self, authenticated_client: AsyncClient) -> AsyncClient:
|
||||
"""性能测试客户端"""
|
||||
return authenticated_client
|
||||
|
||||
@pytest.fixture
|
||||
def performance_thresholds(self):
|
||||
"""性能阈值配置"""
|
||||
return {
|
||||
"response_time_p95": 2000, # 95%的请求响应时间应小于2秒
|
||||
"response_time_p99": 5000, # 99%的请求响应时间应小于5秒
|
||||
"error_rate": 0.05, # 错误率应小于5%
|
||||
"throughput_min": 10, # 最小吞吐量(请求/秒)
|
||||
}
|
||||
|
||||
async def measure_request_time(self, client: AsyncClient, method: str,
|
||||
url: str, **kwargs) -> float:
|
||||
"""测量单个请求时间"""
|
||||
start_time = time.time()
|
||||
|
||||
if method.upper() == "GET":
|
||||
response = await client.get(url, **kwargs)
|
||||
elif method.upper() == "POST":
|
||||
response = await client.post(url, **kwargs)
|
||||
elif method.upper() == "PUT":
|
||||
response = await client.put(url, **kwargs)
|
||||
elif method.upper() == "DELETE":
|
||||
response = await client.delete(url, **kwargs)
|
||||
else:
|
||||
raise ValueError(f"Unsupported method: {method}")
|
||||
|
||||
end_time = time.time()
|
||||
response_time = (end_time - start_time) * 1000 # 转换为毫秒
|
||||
|
||||
return response_time
|
||||
|
||||
async def measure_concurrent_requests(self, client: AsyncClient, method: str,
|
||||
url: str, concurrency: int = 10,
|
||||
**kwargs) -> Dict[str, Any]:
|
||||
"""测量并发请求性能"""
|
||||
async def make_request():
|
||||
return await self.measure_request_time(client, method, url, **kwargs)
|
||||
|
||||
start_time = time.time()
|
||||
results = await asyncio.gather(*[make_request() for _ in range(concurrency)])
|
||||
end_time = time.time()
|
||||
|
||||
total_time = (end_time - start_time) * 1000 # 毫秒
|
||||
response_times = results
|
||||
|
||||
return {
|
||||
"concurrency": concurrency,
|
||||
"total_time_ms": total_time,
|
||||
"response_times_ms": response_times,
|
||||
"min_time_ms": min(response_times),
|
||||
"max_time_ms": max(response_times),
|
||||
"avg_time_ms": statistics.mean(response_times),
|
||||
"median_time_ms": statistics.median(response_times),
|
||||
"p95_time_ms": self._percentile(response_times, 95),
|
||||
"p99_time_ms": self._percentile(response_times, 99),
|
||||
"throughput_rps": concurrency / (total_time / 1000),
|
||||
"success_count": len(response_times),
|
||||
}
|
||||
|
||||
def _percentile(self, data: List[float], percentile: float) -> float:
|
||||
"""计算百分位数"""
|
||||
sorted_data = sorted(data)
|
||||
index = int(len(sorted_data) * percentile / 100)
|
||||
return sorted_data[min(index, len(sorted_data) - 1)]
|
||||
|
||||
def assert_performance(self, results: Dict[str, Any], thresholds: Dict[str, Any]):
|
||||
"""断言性能指标"""
|
||||
p95_time = results["p95_time_ms"]
|
||||
p99_time = results["p99_time_ms"]
|
||||
throughput = results["throughput_rps"]
|
||||
|
||||
if p95_time > thresholds["response_time_p95"]:
|
||||
pytest.fail(f"P95响应时间 {p95_time:.2f}ms 超过阈值 {thresholds['response_time_p95']}ms")
|
||||
|
||||
if p99_time > thresholds["response_time_p99"]:
|
||||
pytest.fail(f"P99响应时间 {p99_time:.2f}ms 超过阈值 {thresholds['response_time_p99']}ms")
|
||||
|
||||
if throughput < thresholds["throughput_min"]:
|
||||
pytest.fail(f"吞吐量 {throughput:.2f} rps 低于最小值 {thresholds['throughput_min']} rps")
|
||||
|
||||
logger.info(f"性能测试通过: P95={p95_time:.2f}ms, P99={p99_time:.2f}ms, 吞吐量={throughput:.2f} rps")
|
||||
|
||||
|
||||
@pytest.mark.performance
|
||||
@pytest.mark.slow
|
||||
class TestAPIPerformance(PerformanceTest):
|
||||
"""API性能测试"""
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_user_list_performance(self, perf_client: AsyncClient, performance_thresholds):
|
||||
"""测试用户列表API性能"""
|
||||
results = await self.measure_concurrent_requests(
|
||||
perf_client, "GET", "/api/users", concurrency=20
|
||||
)
|
||||
|
||||
self.assert_performance(results, performance_thresholds)
|
||||
logger.info(f"用户列表API性能: {results}")
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_role_list_performance(self, perf_client: AsyncClient, performance_thresholds):
|
||||
"""测试角色列表API性能"""
|
||||
results = await self.measure_concurrent_requests(
|
||||
perf_client, "GET", "/api/roles", concurrency=20
|
||||
)
|
||||
|
||||
self.assert_performance(results, performance_thresholds)
|
||||
logger.info(f"角色列表API性能: {results}")
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_notice_list_performance(self, perf_client: AsyncClient, performance_thresholds):
|
||||
"""测试通知列表API性能"""
|
||||
results = await self.measure_concurrent_requests(
|
||||
perf_client, "GET", "/api/notices", concurrency=20
|
||||
)
|
||||
|
||||
self.assert_performance(results, performance_thresholds)
|
||||
logger.info(f"通知列表API性能: {results}")
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_search_performance(self, perf_client: AsyncClient, performance_thresholds):
|
||||
"""测试搜索API性能"""
|
||||
results = await self.measure_concurrent_requests(
|
||||
perf_client, "GET", "/api/users/page?keyword=test", concurrency=15
|
||||
)
|
||||
|
||||
self.assert_performance(results, performance_thresholds)
|
||||
logger.info(f"搜索API性能: {results}")
|
||||
|
||||
|
||||
@pytest.mark.performance
|
||||
@pytest.mark.slow
|
||||
class TestLoadTesting(PerformanceTest):
|
||||
"""负载测试"""
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_sustained_load(self, perf_client: AsyncClient):
|
||||
"""测试持续负载"""
|
||||
duration_seconds = 30
|
||||
requests_per_second = 5
|
||||
total_requests = duration_seconds * requests_per_second
|
||||
|
||||
response_times = []
|
||||
start_time = time.time()
|
||||
|
||||
for i in range(total_requests):
|
||||
response_time = await self.measure_request_time(
|
||||
perf_client, "GET", "/api/users"
|
||||
)
|
||||
response_times.append(response_time)
|
||||
|
||||
elapsed = time.time() - start_time
|
||||
if elapsed < duration_seconds:
|
||||
sleep_time = max(0, (i + 1) / requests_per_second - elapsed)
|
||||
await asyncio.sleep(max(0, sleep_time))
|
||||
|
||||
avg_time = statistics.mean(response_times)
|
||||
p95_time = self._percentile(response_times, 95)
|
||||
|
||||
logger.info(f"持续负载测试 - 平均响应时间: {avg_time:.2f}ms, P95: {p95_time:.2f}ms")
|
||||
|
||||
assert avg_time < 3000, f"平均响应时间 {avg_time:.2f}ms 超过阈值 3000ms"
|
||||
assert p95_time < 5000, f"P95响应时间 {p95_time:.2f}ms 超过阈值 5000ms"
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_spike_load(self, perf_client: AsyncClient):
|
||||
"""测试突发负载"""
|
||||
spike_sizes = [10, 50, 100, 50, 10]
|
||||
|
||||
for spike_size in spike_sizes:
|
||||
results = await self.measure_concurrent_requests(
|
||||
perf_client, "GET", "/api/users", concurrency=spike_size
|
||||
)
|
||||
|
||||
logger.info(f"突发负载测试 (并发={spike_size}): P95={results['p95_time_ms']:.2f}ms")
|
||||
|
||||
assert results["p95_time_ms"] < 10000, \
|
||||
f"突发负载 {spike_size} 并发时 P95响应时间超时"
|
||||
@@ -0,0 +1,274 @@
|
||||
"""
|
||||
权限管理增强测试用例
|
||||
"""
|
||||
|
||||
import pytest
|
||||
from api.role_api import RoleAPI
|
||||
from api.user_api import UserAPI
|
||||
|
||||
|
||||
@pytest.mark.permission
|
||||
@pytest.mark.regression
|
||||
class TestPermission:
|
||||
"""权限管理测试类"""
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_user_role_assignment(self, authenticated_client, test_user_data, test_role_data, cleanup_user, cleanup_role):
|
||||
"""测试用户角色分配"""
|
||||
user_api = UserAPI(authenticated_client)
|
||||
role_api = RoleAPI(authenticated_client)
|
||||
|
||||
user_response = await user_api.create_user(test_user_data)
|
||||
user_id = user_response.json()["id"]
|
||||
|
||||
role_response = await role_api.create_role(test_role_data)
|
||||
role_id = role_response.json()["id"]
|
||||
|
||||
update_data = {"roleId": role_id}
|
||||
response = await user_api.update_user(user_id, update_data)
|
||||
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["roleId"] == role_id
|
||||
|
||||
cleanup_user.append(user_id)
|
||||
cleanup_role.append(role_id)
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_user_role_removal(self, authenticated_client, test_user_data, test_role_data, cleanup_user, cleanup_role):
|
||||
"""测试用户角色移除"""
|
||||
user_api = UserAPI(authenticated_client)
|
||||
role_api = RoleAPI(authenticated_client)
|
||||
|
||||
user_response = await user_api.create_user(test_user_data)
|
||||
user_id = user_response.json()["id"]
|
||||
|
||||
role_response = await role_api.create_role(test_role_data)
|
||||
role_id = role_response.json()["id"]
|
||||
|
||||
await user_api.update_user(user_id, {"roleId": role_id})
|
||||
|
||||
response = await user_api.update_user(user_id, {"roleId": None})
|
||||
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["roleId"] is None
|
||||
|
||||
cleanup_user.append(user_id)
|
||||
cleanup_role.append(role_id)
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_role_status_permission(self, authenticated_client, test_role_data, cleanup_role):
|
||||
"""测试角色状态权限控制"""
|
||||
role_api = RoleAPI(authenticated_client)
|
||||
|
||||
create_response = await role_api.create_role(test_role_data)
|
||||
role_id = create_response.json()["id"]
|
||||
|
||||
response = await role_api.update_role(role_id, {"status": 0})
|
||||
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["status"] == 0
|
||||
|
||||
cleanup_role.append(role_id)
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_multiple_users_same_role(self, authenticated_client, test_user_data, test_role_data, cleanup_user, cleanup_role):
|
||||
"""测试多个用户分配相同角色"""
|
||||
user_api = UserAPI(authenticated_client)
|
||||
role_api = RoleAPI(authenticated_client)
|
||||
|
||||
role_response = await role_api.create_role(test_role_data)
|
||||
role_id = role_response.json()["id"]
|
||||
|
||||
user_ids = []
|
||||
for i in range(3):
|
||||
import time
|
||||
timestamp = int(time.time() * 1000)
|
||||
user_data = test_user_data.copy()
|
||||
user_data["username"] = f"testuser_{timestamp}_{i}"
|
||||
user_data["email"] = f"test_{timestamp}_{i}@example.com"
|
||||
|
||||
user_response = await user_api.create_user(user_data)
|
||||
user_id = user_response.json()["id"]
|
||||
user_ids.append(user_id)
|
||||
|
||||
await user_api.update_user(user_id, {"roleId": role_id})
|
||||
|
||||
for user_id in user_ids:
|
||||
user_response = await user_api.get_user_by_id(user_id)
|
||||
assert user_response.json()["roleId"] == role_id
|
||||
|
||||
cleanup_user.extend(user_ids)
|
||||
cleanup_role.append(role_id)
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_role_hierarchy(self, authenticated_client, cleanup_role):
|
||||
"""测试角色层级"""
|
||||
role_api = RoleAPI(authenticated_client)
|
||||
|
||||
import time
|
||||
timestamp = int(time.time() * 1000)
|
||||
|
||||
admin_role_data = {
|
||||
"roleName": f"Admin_{timestamp}",
|
||||
"roleKey": f"admin_{timestamp}",
|
||||
"roleSort": 1,
|
||||
"status": 1
|
||||
}
|
||||
admin_response = await role_api.create_role(admin_role_data)
|
||||
admin_id = admin_response.json()["id"]
|
||||
|
||||
user_role_data = {
|
||||
"roleName": f"User_{timestamp}",
|
||||
"roleKey": f"user_{timestamp}",
|
||||
"roleSort": 2,
|
||||
"status": 1
|
||||
}
|
||||
user_response = await role_api.create_role(user_role_data)
|
||||
user_id = user_response.json()["id"]
|
||||
|
||||
all_roles = await role_api.get_all_roles()
|
||||
roles_data = all_roles.json()
|
||||
role_sorts = [role["roleSort"] for role in roles_data]
|
||||
|
||||
assert 1 in role_sorts
|
||||
assert 2 in role_sorts
|
||||
|
||||
cleanup_role.extend([admin_id, user_id])
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_permission_inheritance(self, authenticated_client, test_user_data, test_role_data, cleanup_user, cleanup_role):
|
||||
"""测试权限继承"""
|
||||
user_api = UserAPI(authenticated_client)
|
||||
role_api = RoleAPI(authenticated_client)
|
||||
|
||||
role_response = await role_api.create_role(test_role_data)
|
||||
role_id = role_response.json()["id"]
|
||||
|
||||
user_response = await user_api.create_user(test_user_data)
|
||||
user_id = user_response.json()["id"]
|
||||
|
||||
await user_api.update_user(user_id, {"roleId": role_id})
|
||||
|
||||
user_data = await user_api.get_user_by_id(user_id)
|
||||
assert user_data.json()["roleId"] == role_id
|
||||
|
||||
role_data = await role_api.get_role_by_id(role_id)
|
||||
assert role_data.json()["id"] == role_id
|
||||
|
||||
cleanup_user.append(user_id)
|
||||
cleanup_role.append(role_id)
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_role_sort_order(self, authenticated_client, cleanup_role):
|
||||
"""测试角色排序"""
|
||||
role_api = RoleAPI(authenticated_client)
|
||||
|
||||
import time
|
||||
timestamp = int(time.time() * 1000)
|
||||
|
||||
role1_data = {
|
||||
"roleName": f"Role1_{timestamp}",
|
||||
"roleKey": f"role1_{timestamp}",
|
||||
"roleSort": 3,
|
||||
"status": 1
|
||||
}
|
||||
role1_response = await role_api.create_role(role1_data)
|
||||
role1_id = role1_response.json()["id"]
|
||||
|
||||
role2_data = {
|
||||
"roleName": f"Role2_{timestamp}",
|
||||
"roleKey": f"role2_{timestamp}",
|
||||
"roleSort": 1,
|
||||
"status": 1
|
||||
}
|
||||
role2_response = await role_api.create_role(role2_data)
|
||||
role2_id = role2_response.json()["id"]
|
||||
|
||||
role3_data = {
|
||||
"roleName": f"Role3_{timestamp}",
|
||||
"roleKey": f"role3_{timestamp}",
|
||||
"roleSort": 2,
|
||||
"status": 1
|
||||
}
|
||||
role3_response = await role_api.create_role(role3_data)
|
||||
role3_id = role3_response.json()["id"]
|
||||
|
||||
response = await role_api.get_roles_by_page(page=0, size=10, sort="roleSort", order="asc")
|
||||
roles = response.json()["content"]
|
||||
|
||||
role_sorts = [role["roleSort"] for role in roles]
|
||||
assert role_sorts == sorted(role_sorts)
|
||||
|
||||
cleanup_role.extend([role1_id, role2_id, role3_id])
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_disabled_role_access(self, authenticated_client, test_user_data, test_role_data, cleanup_user, cleanup_role):
|
||||
"""测试禁用角色的访问控制"""
|
||||
user_api = UserAPI(authenticated_client)
|
||||
role_api = RoleAPI(authenticated_client)
|
||||
|
||||
role_response = await role_api.create_role(test_role_data)
|
||||
role_id = role_response.json()["id"]
|
||||
|
||||
user_response = await user_api.create_user(test_user_data)
|
||||
user_id = user_response.json()["id"]
|
||||
|
||||
await user_api.update_user(user_id, {"roleId": role_id})
|
||||
|
||||
await role_api.update_role(role_id, {"status": 0})
|
||||
|
||||
role_data = await role_api.get_role_by_id(role_id)
|
||||
assert role_data.json()["status"] == 0
|
||||
|
||||
cleanup_user.append(user_id)
|
||||
cleanup_role.append(role_id)
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_role_uniqueness(self, authenticated_client, cleanup_role):
|
||||
"""测试角色唯一性约束"""
|
||||
role_api = RoleAPI(authenticated_client)
|
||||
|
||||
import time
|
||||
timestamp = int(time.time() * 1000)
|
||||
|
||||
role_data = {
|
||||
"roleName": f"UniqueRole_{timestamp}",
|
||||
"roleKey": f"unique_role_{timestamp}",
|
||||
"roleSort": 1,
|
||||
"status": 1
|
||||
}
|
||||
|
||||
response1 = await role_api.create_role(role_data)
|
||||
assert response1.status_code == 201
|
||||
role_id = response1.json()["id"]
|
||||
|
||||
response2 = await role_api.create_role(role_data)
|
||||
assert response2.status_code in [400, 409]
|
||||
|
||||
cleanup_role.append(role_id)
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_role_deletion_with_users(self, authenticated_client, test_user_data, test_role_data, cleanup_user, cleanup_role):
|
||||
"""测试删除有用户的角色"""
|
||||
user_api = UserAPI(authenticated_client)
|
||||
role_api = RoleAPI(authenticated_client)
|
||||
|
||||
role_response = await role_api.create_role(test_role_data)
|
||||
role_id = role_response.json()["id"]
|
||||
|
||||
user_response = await user_api.create_user(test_user_data)
|
||||
user_id = user_response.json()["id"]
|
||||
|
||||
await user_api.update_user(user_id, {"roleId": role_id})
|
||||
|
||||
delete_response = await role_api.delete_role(role_id)
|
||||
assert delete_response.status_code == 200
|
||||
|
||||
user_data = await user_api.get_user_by_id(user_id)
|
||||
assert user_data.json()["roleId"] is None
|
||||
|
||||
cleanup_user.append(user_id)
|
||||
cleanup_role.append(role_id)
|
||||
@@ -0,0 +1,188 @@
|
||||
"""
|
||||
WebSocket实时通信测试用例
|
||||
"""
|
||||
|
||||
import pytest
|
||||
import asyncio
|
||||
import json
|
||||
from websockets.client import connect
|
||||
from websockets.exceptions import ConnectionClosed
|
||||
|
||||
|
||||
@pytest.mark.websocket
|
||||
@pytest.mark.regression
|
||||
class TestWebSocket:
|
||||
"""WebSocket实时通信测试类"""
|
||||
|
||||
@pytest.fixture
|
||||
def websocket_url(self):
|
||||
"""WebSocket连接URL"""
|
||||
return "ws://localhost:8080/ws"
|
||||
|
||||
@pytest.fixture
|
||||
async def websocket_connection(self, websocket_url):
|
||||
"""WebSocket连接fixture"""
|
||||
async with connect(websocket_url) as websocket:
|
||||
yield websocket
|
||||
|
||||
@pytest.fixture
|
||||
async def authenticated_websocket(self, websocket_url, authenticated_client):
|
||||
"""带认证的WebSocket连接"""
|
||||
token = authenticated_client.headers.get("Authorization")
|
||||
url_with_token = f"{websocket_url}?token={token.replace('Bearer ', '')}"
|
||||
|
||||
async with connect(url_with_token) as websocket:
|
||||
yield websocket
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_websocket_connection(self, websocket_url):
|
||||
"""测试WebSocket连接建立"""
|
||||
try:
|
||||
async with connect(websocket_url) as websocket:
|
||||
assert websocket.open
|
||||
except ConnectionRefusedError:
|
||||
pytest.skip("WebSocket服务未启动")
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_websocket_ping_pong(self, websocket_connection):
|
||||
"""测试WebSocket心跳机制"""
|
||||
ping_message = {
|
||||
"type": "ping",
|
||||
"timestamp": 1234567890
|
||||
}
|
||||
|
||||
await websocket_connection.send(json.dumps(ping_message))
|
||||
|
||||
response = await asyncio.wait_for(websocket_connection.recv(), timeout=5.0)
|
||||
pong_message = json.loads(response)
|
||||
|
||||
assert pong_message["type"] == "pong"
|
||||
assert "timestamp" in pong_message
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_websocket_subscribe(self, websocket_connection):
|
||||
"""测试WebSocket订阅"""
|
||||
subscribe_message = {
|
||||
"type": "subscribe",
|
||||
"channel": "notifications"
|
||||
}
|
||||
|
||||
await websocket_connection.send(json.dumps(subscribe_message))
|
||||
|
||||
response = await asyncio.wait_for(websocket_connection.recv(), timeout=5.0)
|
||||
message = json.loads(response)
|
||||
|
||||
assert message["type"] in ["subscribe_ack", "pong"]
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_websocket_multiple_messages(self, websocket_connection):
|
||||
"""测试WebSocket多消息处理"""
|
||||
messages = [
|
||||
{"type": "ping"},
|
||||
{"type": "subscribe", "channel": "test"},
|
||||
{"type": "ping"}
|
||||
]
|
||||
|
||||
responses = []
|
||||
for msg in messages:
|
||||
await websocket_connection.send(json.dumps(msg))
|
||||
try:
|
||||
response = await asyncio.wait_for(websocket_connection.recv(), timeout=2.0)
|
||||
responses.append(json.loads(response))
|
||||
except asyncio.TimeoutError:
|
||||
pass
|
||||
|
||||
assert len(responses) >= 2
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_websocket_invalid_message(self, websocket_connection):
|
||||
"""测试WebSocket无效消息处理"""
|
||||
invalid_messages = [
|
||||
"invalid json",
|
||||
"",
|
||||
json.dumps({"type": "unknown_type"}),
|
||||
json.dumps({})
|
||||
]
|
||||
|
||||
for msg in invalid_messages:
|
||||
try:
|
||||
await websocket_connection.send(msg)
|
||||
await asyncio.sleep(0.5)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_websocket_connection_close(self, websocket_url):
|
||||
"""测试WebSocket连接关闭"""
|
||||
async with connect(websocket_url) as websocket:
|
||||
assert websocket.open
|
||||
await websocket.close()
|
||||
assert not websocket.open
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_websocket_timeout(self, websocket_url):
|
||||
"""测试WebSocket超时"""
|
||||
try:
|
||||
async with connect(websocket_url, ping_timeout=2.0) as websocket:
|
||||
await asyncio.sleep(3.0)
|
||||
except (ConnectionClosed, asyncio.TimeoutError):
|
||||
pass
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_websocket_concurrent_connections(self, websocket_url):
|
||||
"""测试WebSocket并发连接"""
|
||||
async def create_connection():
|
||||
try:
|
||||
async with connect(websocket_url) as websocket:
|
||||
await websocket.send(json.dumps({"type": "ping"}))
|
||||
await asyncio.wait_for(websocket_connection.recv(), timeout=2.0)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
connections = [create_connection() for _ in range(5)]
|
||||
await asyncio.gather(*connections, return_exceptions=True)
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_websocket_large_message(self, websocket_connection):
|
||||
"""测试WebSocket大消息处理"""
|
||||
large_data = "x" * 10000
|
||||
message = {
|
||||
"type": "test",
|
||||
"data": large_data
|
||||
}
|
||||
|
||||
await websocket_connection.send(json.dumps(message))
|
||||
|
||||
try:
|
||||
response = await asyncio.wait_for(websocket_connection.recv(), timeout=5.0)
|
||||
assert response
|
||||
except asyncio.TimeoutError:
|
||||
pass
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_websocket_reconnect(self, websocket_url):
|
||||
"""测试WebSocket重连"""
|
||||
for i in range(3):
|
||||
try:
|
||||
async with connect(websocket_url) as websocket:
|
||||
await websocket.send(json.dumps({"type": "ping"}))
|
||||
response = await asyncio.wait_for(websocket.recv(), timeout=2.0)
|
||||
assert response
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_websocket_unicode_message(self, websocket_connection):
|
||||
"""测试WebSocket Unicode消息"""
|
||||
unicode_message = {
|
||||
"type": "test",
|
||||
"content": "测试中文🎉🚀"
|
||||
}
|
||||
|
||||
await websocket_connection.send(json.dumps(unicode_message))
|
||||
|
||||
try:
|
||||
response = await asyncio.wait_for(websocket_connection.recv(), timeout=2.0)
|
||||
assert response
|
||||
except asyncio.TimeoutError:
|
||||
pass
|
||||
Reference in New Issue
Block a user