""" 系统模块单元测试 (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, tenant_id=fake_user.tenant_id, ) # ==================== 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="

内容

", 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 "db" in AuthSchema.model_fields assert "tenant_id" 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")