"""SYS-001 ~ SYS-016: 系统用户、角色、菜单管理测试。 覆盖功能点: - 用户 CRUD(新增、停用、重置密码) - 角色 CRUD(新增、编码重复) - 菜单管理 - 角色授权 - 权限越权测试 """ from __future__ import annotations import pytest @pytest.mark.system @pytest.mark.p0 class TestUserManagement: """SYS-001 ~ SYS-005: 用户管理测试。""" def test_create_user_success(self, client, admin_headers, seed_roles): """SYS-001: 新增用户成功。""" resp = client.post("/api/system/users", headers=admin_headers, json={ "username": "newuser01", "password": "password123", "real_name": "新用户", "mobile": "13800009001", "role_id": 2, }) assert resp.status_code == 200 data = resp.json() assert data["code"] == 0 def test_duplicate_username(self, client, admin_headers, seed_users): """SYS-002: 用户账号重复应返回 40006。""" resp = client.post("/api/system/users", headers=admin_headers, json={ "username": "admin01", "password": "password123", "real_name": "重复用户", "role_id": 1, }) assert resp.status_code in (400, 409) def test_disable_user(self, client, admin_headers, seed_users, db_session): """SYS-004: 停用用户成功。 实际接口: POST /api/system/users/{user_id}/status 请求体: {"status": 0} """ from backend.app.models.system import User user = db_session.query(User).filter(User.username == "sales01").first() resp = client.post(f"/api/system/users/{user.id}/status", headers=admin_headers, json={ "status": 0, }) assert resp.status_code == 200 def test_reset_password(self, client, admin_headers, seed_users, db_session): """SYS-005: 重置密码成功。 实际接口: POST /api/system/users/{user_id}/reset-password 请求体: {"new_password": "xxx"} """ from backend.app.models.system import User user = db_session.query(User).filter(User.username == "sales01").first() resp = client.post(f"/api/system/users/{user.id}/reset-password", headers=admin_headers, json={ "new_password": "newpassword123", }) assert resp.status_code == 200 def test_user_list(self, client, admin_headers, seed_users): """SYS-015: 用户列表查询。""" resp = client.get("/api/system/users", headers=admin_headers) assert resp.status_code == 200 data = resp.json() assert "list" in data["data"] @pytest.mark.system @pytest.mark.p0 class TestRoleManagement: """SYS-006 ~ SYS-008: 角色管理测试。""" def test_create_role_success(self, client, admin_headers): """SYS-006: 新增角色成功。""" resp = client.post("/api/system/roles", headers=admin_headers, json={ "role_name": "测试角色", "role_code": "test_role", "status": 1, }) assert resp.status_code == 200 data = resp.json() assert data["code"] == 0 def test_duplicate_role_code(self, client, admin_headers, seed_roles): """SYS-007: 角色编码重复应返回 40006。""" resp = client.post("/api/system/roles", headers=admin_headers, json={ "role_name": "重复角色", "role_code": "admin", "status": 1, }) assert resp.status_code in (400, 409) def test_role_list(self, client, admin_headers, seed_roles): """角色列表查询。 注意: seed_roles fixture 创建了 5 个角色, 但 seed_data 也会创建角色(通过 seed_menus 依赖 seed_users), 实际数量取决于 fixture 顺序。 """ resp = client.get("/api/system/roles", headers=admin_headers) assert resp.status_code == 200 data = resp.json() # 至少有 seed_roles 创建的 5 个角色 roles = data["data"] if isinstance(data["data"], list) else data["data"].get("list", []) assert len(roles) >= 5 @pytest.mark.system @pytest.mark.p1 class TestMenuManagement: """SYS-009 ~ SYS-011: 菜单管理测试。""" def test_create_menu_success(self, client, admin_headers): """SYS-009: 新增菜单成功。""" resp = client.post("/api/system/menus", headers=admin_headers, json={ "menu_name": "测试菜单", "menu_path": "/test", "menu_type": "page", "permission_code": "test:menu:view", "sort_no": 99, }) assert resp.status_code == 200 data = resp.json() assert data["code"] == 0 def test_duplicate_permission_code(self, client, admin_headers, seed_menus): """SYS-010: 菜单权限编码重复应返回 40006。""" resp = client.post("/api/system/menus", headers=admin_headers, json={ "menu_name": "重复菜单", "menu_path": "/test2", "menu_type": "page", "permission_code": "order:list", "sort_no": 100, }) assert resp.status_code in (400, 409) def test_menu_tree(self, client, admin_headers, seed_menus): """菜单树查询。""" resp = client.get("/api/system/menus", headers=admin_headers) assert resp.status_code == 200 @pytest.mark.system @pytest.mark.p0 class TestRoleAuthorization: """SYS-012: 角色授权测试。""" def test_save_role_menus(self, client, admin_headers, seed_roles, seed_menus): """SYS-012: 角色授权保存成功。 实际接口: PUT /api/system/roles/{role_id}/menus """ role_id = 2 # 业务员角色 resp = client.put(f"/api/system/roles/{role_id}/menus", headers=admin_headers, json={ "menu_ids": [1, 2, 4], }) assert resp.status_code == 200 @pytest.mark.system @pytest.mark.p0 class TestPermissionBoundary: """SYS-013: 权限越权测试。""" def test_salesman_cannot_access_system(self, client, salesman_headers): """SYS-013: 业务员不能访问系统管理接口。""" resp = client.get("/api/system/users", headers=salesman_headers) assert resp.status_code == 403 def test_driver_cannot_access_system(self, client, driver_headers): """司机不能访问系统管理接口。""" resp = client.get("/api/system/users", headers=driver_headers) assert resp.status_code == 403 def test_manager_cannot_manage_users(self, client, manager_headers): """管理层不能管理用户。""" resp = client.post("/api/system/users", headers=manager_headers, json={ "username": "test", "password": "test", "real_name": "test", "role_id": 1, }) assert resp.status_code == 403 def test_admin_full_access(self, client, admin_headers): """管理员可以访问所有系统管理接口。""" resp = client.get("/api/system/users", headers=admin_headers) assert resp.status_code == 200 resp = client.get("/api/system/roles", headers=admin_headers) assert resp.status_code == 200 resp = client.get("/api/system/menus", headers=admin_headers) assert resp.status_code == 200