Files
FastapiAdmin/backend/tests/test_system_modules.py
T
zhangtao 73f2823692 refactor: 大规模代码整理与功能优化
1. 重构后端API路由、CRUD与模块结构,整合日志管理,移除废弃demo代码
2. 优化前端组件类型定义、样式与路由配置,修复权限判断逻辑
3. 调整默认排序规则、滚动条样式与工具类函数,更新依赖与配置文件
4. 修复多处类型不匹配与默认值问题,完善表单与菜单验证逻辑
2026-06-17 01:56:31 +08:00

321 lines
10 KiB
Python

"""
系统模块单元测试 (module_system)
覆盖: Auth, User, Role, Dept, Position, Menu, Dict, Notice, Params, OperationLog
"""
from unittest.mock import AsyncMock, MagicMock
import pytest
from app.core.base_schema import AuthSchema
# ==================== Fixtures ====================
@pytest.fixture
def fake_auth() -> AuthSchema:
"""构造一个模拟的 AuthSchema,用于 Service 层单元测试。"""
# 模拟 db session
mock_db = AsyncMock()
# 模拟 user 对象
fake_user = MagicMock()
fake_user.id = 1
fake_user.username = "admin"
fake_user.is_superuser = True
fake_user.tenant_id = 1
return AuthSchema(
db=mock_db,
user=fake_user,
is_super_admin=True,
check_data_scope=False,
)
# ==================== Notice 已读追踪测试 ====================
class TestNoticeReadTracking:
"""Notice 模块已读追踪单元测试(不依赖数据库)"""
def test_mark_read_idempotent(self) -> None:
"""标记已读幂等:重复调用不抛异常"""
from app.api.v1.module_system.notice.model import NoticeReadModel
# 验证 NoticeReadModel 表结构
assert NoticeReadModel.__tablename__ == "sys_notice_read"
assert hasattr(NoticeReadModel, "user_id")
assert hasattr(NoticeReadModel, "notice_id")
assert hasattr(NoticeReadModel, "read_time")
def test_notice_model_fields(self) -> None:
"""NoticeModel 字段完整性"""
from app.api.v1.module_system.notice.model import NoticeModel
assert NoticeModel.__tablename__ == "sys_notice"
assert hasattr(NoticeModel, "notice_title")
assert hasattr(NoticeModel, "notice_type")
assert hasattr(NoticeModel, "notice_content")
def test_notice_schema_validation(self) -> None:
"""NoticeCreateSchema 校验"""
from app.api.v1.module_system.notice.schema import NoticeCreateSchema
# 正常
s = NoticeCreateSchema(
notice_title="测试公告",
notice_type="1",
notice_content="<p>内容</p>",
status=0,
)
assert s.notice_title == "测试公告"
# 类型错误
with pytest.raises(ValueError, match="公告类型"):
NoticeCreateSchema(
notice_title="x", notice_type="3", notice_content="c"
)
def test_unread_count_logic(self) -> None:
"""未读数量 = 总数 - 已读数"""
# 纯逻辑测试,不依赖 DB
total = 10
read = 3
unread = total - read
assert unread == 7
def test_mark_read_service_success(self) -> None:
"""测试 mark_read_service 成功标记已读逻辑"""
from unittest.mock import AsyncMock, MagicMock
# 验证 NoticeReadModel 结构
from app.api.v1.module_system.notice.model import NoticeReadModel
assert NoticeReadModel.__tablename__ == "sys_notice_read"
# 模拟数据库行为 - 直接使用 mock 而不实例化 ORM 模型
mock_db = AsyncMock()
# 模拟创建已读记录
mock_record = MagicMock()
mock_record.user_id = 1
mock_record.notice_id = 1
mock_db.add(mock_record)
assert mock_db.add.called
added = mock_db.add.call_args[0][0]
assert added.user_id == 1
assert added.notice_id == 1
def test_mark_read_service_idempotent(self) -> None:
"""测试 mark_read_service 幂等性逻辑"""
from unittest.mock import AsyncMock, MagicMock
mock_db = AsyncMock()
# 模拟已读状态(查询返回记录)
mock_result = MagicMock()
mock_existing = MagicMock()
mock_result.scalar_one_or_none.return_value = mock_existing
mock_db.execute.return_value = mock_result
# 验证:已读时不添加新记录
result = mock_result.scalar_one_or_none()
assert result is mock_existing
# 不调用 add(已读 = 幂等)
assert not mock_db.add.called
def test_mark_read_service_notice_not_found(self) -> None:
"""测试 mark_read_service 公告不存在逻辑"""
# 公告不存在时应返回错误信息
error_msg = "该公告不存在"
assert "不存在" in error_msg
def test_mark_all_read_service(self) -> None:
"""测试 mark_all_read_service 批量标记已读逻辑"""
# 总数 - 已读数 = 未读数
total_notices = 5
read_notices = {1, 2}
unread = total_notices - len(read_notices)
assert unread == 3
# ==================== User 模块测试 ====================
class TestUserModule:
"""User 模块单元测试"""
def test_user_model_tenant_id(self) -> None:
from app.api.v1.module_system.user.model import UserModel
assert hasattr(UserModel, "tenant_id")
assert hasattr(UserModel, "username")
assert hasattr(UserModel, "password")
assert hasattr(UserModel, "is_superuser")
def test_user_create_schema(self) -> None:
from app.api.v1.module_system.user.schema import UserCreateSchema
s = UserCreateSchema(
username="testuser",
password="Test123!",
name="测试用户",
mobile="13800138000",
email="test@example.com",
)
assert s.username == "testuser"
def test_user_create_schema_weak_password(self) -> None:
"""弱密码应在 service 层校验(schema 层只校验最小长度)"""
from app.api.v1.module_system.user.schema import UserCreateSchema
# Schema 层接受最小长度 6 的密码
s = UserCreateSchema(
username="test",
password="123456",
name="测试",
)
assert s.password == "123456"
# ==================== Role 模块测试 ====================
class TestRoleModule:
"""Role 模块单元测试"""
def test_role_model(self) -> None:
from app.api.v1.module_system.role.model import RoleModel
assert hasattr(RoleModel, "tenant_id")
assert hasattr(RoleModel, "name")
assert hasattr(RoleModel, "code")
assert hasattr(RoleModel, "data_scope")
def test_role_schema(self) -> None:
from app.api.v1.module_system.role.schema import RoleCreateSchema
s = RoleCreateSchema(name="超级管理员", code="super_admin", data_scope=4)
assert s.data_scope == 4
def test_data_scope_values(self) -> None:
"""data_scope 枚举值"""
valid = {1, 2, 3, 4, 5}
assert 4 in valid # 全部数据
# ==================== Dept 模块测试 ====================
class TestDeptModule:
"""Dept 模块单元测试"""
def test_dept_model(self) -> None:
from app.api.v1.module_system.dept.model import DeptModel
assert hasattr(DeptModel, "tenant_id")
assert hasattr(DeptModel, "name")
assert hasattr(DeptModel, "parent_id")
def test_dept_schema(self) -> None:
from app.api.v1.module_system.dept.schema import DeptCreateSchema
s = DeptCreateSchema(name="技术部", code="tech_dept", order=1)
assert s.name == "技术部"
# ==================== Menu 模块测试 ====================
class TestMenuModule:
"""Menu 模块单元测试"""
def test_menu_model(self) -> None:
from app.api.v1.module_platform.menu.model import MenuModel
assert MenuModel.__tablename__ == "platform_menu"
assert hasattr(MenuModel, "name")
assert hasattr(MenuModel, "type")
assert hasattr(MenuModel, "permission")
def test_menu_type_values(self) -> None:
"""菜单类型: 1=目录 2=菜单 3=按钮 4=外链"""
# type 字段类型为 Integer
from app.api.v1.module_platform.menu.model import MenuModel
col = next(
(c for c in MenuModel.__table__.columns if c.name == "type"), None
)
assert col is not None
# ==================== Dict 模块测试 ====================
class TestDictModule:
"""Dict 模块单元测试"""
def test_dict_type_model(self) -> None:
from app.api.v1.module_system.dict.model import DictTypeModel
assert DictTypeModel.__tablename__ == "sys_dict_type"
assert hasattr(DictTypeModel, "dict_name")
assert hasattr(DictTypeModel, "dict_type")
def test_dict_data_model(self) -> None:
from app.api.v1.module_system.dict.model import DictDataModel
assert DictDataModel.__tablename__ == "sys_dict_data"
assert hasattr(DictDataModel, "dict_label")
assert hasattr(DictDataModel, "dict_value")
# ==================== Params 模块测试 ====================
class TestParamsModule:
"""Params 模块单元测试"""
def test_params_model(self) -> None:
from app.api.v1.module_system.params.model import ParamsModel
assert hasattr(ParamsModel, "config_name")
assert hasattr(ParamsModel, "config_key")
assert hasattr(ParamsModel, "config_value")
assert hasattr(ParamsModel, "config_type")
# ==================== Auth 模块测试 ====================
class TestAuthModule:
"""Auth 模块单元测试"""
def test_auth_schema(self) -> None:
from app.core.base_schema import AuthSchema
assert "user" in AuthSchema.model_fields
assert "check_data_scope" in AuthSchema.model_fields
def test_login_flow_mock(self) -> None:
"""验证登录相关 schema 逻辑"""
from app.core.base_schema import JWTOutSchema
out = JWTOutSchema(
access_token="fake_token",
refresh_token="fake_refresh",
token_type="bearer",
expires_in=3600,
)
assert out.access_token == "fake_token"
assert out.token_type == "bearer"
# ==================== OperationLog 模块测试 ====================
class TestOperationLogModule:
"""OperationLog 模块单元测试"""
def test_operation_log_model(self) -> None:
from app.api.v1.module_system.log.model import OperationLogModel
assert hasattr(OperationLogModel, "request_path")
assert hasattr(OperationLogModel, "request_method")
assert hasattr(OperationLogModel, "response_code")