"""用户管理端点完整测试 — user.py 覆盖端点: GET /api/v1/users 列表(支持 role / is_active 筛选) POST /api/v1/users 创建用户 GET /api/v1/users/{user_id} 获取单个用户 PUT /api/v1/users/{user_id} 更新用户 DELETE /api/v1/users/{user_id} 删除用户 """ import pytest from httpx import AsyncClient from tests.conftest import auth_header # ═══════════════════════════════════════════════════════════════ # GET /users — 列表 # ═══════════════════════════════════════════════════════════════ class TestListUsers: async def test_list_all_users(self, client: AsyncClient, admin_user, admin_token): """管理员可以获取全部用户列表。""" resp = await client.get("/api/v1/users", headers=auth_header(admin_token)) assert resp.status_code == 200 data = resp.json() assert isinstance(data, list) assert len(data) >= 1 async def test_list_users_role_filter(self, client: AsyncClient, admin_user, admin_token): """按 role 筛选用户。""" resp = await client.get( "/api/v1/users?role=admin", headers=auth_header(admin_token), ) assert resp.status_code == 200 for u in resp.json(): assert u["role"] == "admin" async def test_list_users_is_active_filter( self, client: AsyncClient, admin_user, disabled_user, admin_token ): """按 is_active 筛选用户。""" resp = await client.get( "/api/v1/users?is_active=false", headers=auth_header(admin_token), ) assert resp.status_code == 200 for u in resp.json(): assert u["is_active"] is False async def test_list_users_combined_filters( self, client: AsyncClient, admin_user, disabled_user, admin_token ): """同时使用 role + is_active 筛选。""" resp = await client.get( "/api/v1/users?role=developer&is_active=false", headers=auth_header(admin_token), ) assert resp.status_code == 200 for u in resp.json(): assert u["role"] == "developer" assert u["is_active"] is False async def test_list_users_empty_result(self, client: AsyncClient, admin_token): """不存在的角色返回空列表。""" resp = await client.get( "/api/v1/users?role=guest", headers=auth_header(admin_token), ) assert resp.status_code == 200 assert resp.json() == [] async def test_list_users_non_admin_forbidden( self, client: AsyncClient, developer_token ): """developer 角色无权访问用户列表。""" resp = await client.get( "/api/v1/users", headers=auth_header(developer_token) ) assert resp.status_code == 403 async def test_list_users_guest_forbidden( self, client: AsyncClient, guest_token ): """guest 角色无权访问用户列表。""" resp = await client.get( "/api/v1/users", headers=auth_header(guest_token) ) assert resp.status_code == 403 async def test_list_users_unauthenticated(self, client: AsyncClient): """未认证请求返回 401。""" resp = await client.get("/api/v1/users") assert resp.status_code == 401 # ═══════════════════════════════════════════════════════════════ # POST /users — 创建 # ═══════════════════════════════════════════════════════════════ class TestCreateUser: async def test_create_user_success(self, client: AsyncClient, admin_token): """成功创建用户。""" resp = await client.post( "/api/v1/users", headers=auth_header(admin_token), json={ "username": "newuser", "display_name": "新用户", "password": "secure123", "role": "developer", }, ) assert resp.status_code == 201 data = resp.json() assert data["username"] == "newuser" assert data["display_name"] == "新用户" assert data["role"] == "developer" assert "id" in data assert "hashed_password" not in data async def test_create_user_with_all_fields(self, client: AsyncClient, admin_token): """创建用户时填写所有可选字段。""" resp = await client.post( "/api/v1/users", headers=auth_header(admin_token), json={ "username": "fulluser", "display_name": "完整用户", "password": "secure123", "email": "full@example.com", "phone": "13800138000", "role": "dba", }, ) assert resp.status_code == 201 data = resp.json() assert data["email"] == "full@example.com" assert data["phone"] == "13800138000" assert data["role"] == "dba" async def test_create_duplicate_username( self, client: AsyncClient, admin_user, admin_token ): """用户名重复返回 400。""" resp = await client.post( "/api/v1/users", headers=auth_header(admin_token), json={ "username": "admin", "display_name": "重复", "password": "123456", "role": "developer", }, ) assert resp.status_code == 400 assert "已存在" in resp.json()["detail"] async def test_create_user_short_password(self, client: AsyncClient, admin_token): """密码少于 6 位返回 422。""" resp = await client.post( "/api/v1/users", headers=auth_header(admin_token), json={ "username": "shortpw", "display_name": "短密码", "password": "123", "role": "developer", }, ) assert resp.status_code == 422 async def test_create_user_invalid_role(self, client: AsyncClient, admin_token): """无效角色返回 422。""" resp = await client.post( "/api/v1/users", headers=auth_header(admin_token), json={ "username": "badrole", "display_name": "坏角色", "password": "123456", "role": "superadmin", }, ) assert resp.status_code == 422 async def test_create_user_missing_username(self, client: AsyncClient, admin_token): """缺少 username 字段返回 422。""" resp = await client.post( "/api/v1/users", headers=auth_header(admin_token), json={ "display_name": "无用户名", "password": "123456", "role": "developer", }, ) assert resp.status_code == 422 async def test_create_user_non_admin_forbidden( self, client: AsyncClient, developer_token ): """developer 角色无权创建用户。""" resp = await client.post( "/api/v1/users", headers=auth_header(developer_token), json={ "username": "unauthorized", "display_name": "不应成功", "password": "123456", "role": "developer", }, ) assert resp.status_code == 403 async def test_create_user_default_role(self, client: AsyncClient, admin_token): """不传 role 时默认为 developer。""" resp = await client.post( "/api/v1/users", headers=auth_header(admin_token), json={ "username": "defaultrole", "display_name": "默认角色", "password": "123456", }, ) assert resp.status_code == 201 assert resp.json()["role"] == "developer" # ═══════════════════════════════════════════════════════════════ # GET /users/{user_id} — 获取单个 # ═══════════════════════════════════════════════════════════════ class TestGetUser: async def test_get_user_success( self, client: AsyncClient, admin_user, admin_token ): """成功获取单个用户。""" resp = await client.get( f"/api/v1/users/{admin_user.id}", headers=auth_header(admin_token), ) assert resp.status_code == 200 assert resp.json()["username"] == "admin" async def test_get_user_nonexistent(self, client: AsyncClient, admin_token): """查询不存在的用户返回 404。""" resp = await client.get( "/api/v1/users/9999", headers=auth_header(admin_token), ) assert resp.status_code == 404 assert "不存在" in resp.json()["detail"] async def test_get_user_non_admin_forbidden( self, client: AsyncClient, admin_user, developer_token ): """developer 角色无权查看用户详情。""" resp = await client.get( f"/api/v1/users/{admin_user.id}", headers=auth_header(developer_token), ) assert resp.status_code == 403 async def test_get_user_unauthenticated(self, client: AsyncClient, admin_user): """未认证请求返回 401。""" resp = await client.get(f"/api/v1/users/{admin_user.id}") assert resp.status_code == 401 # ═══════════════════════════════════════════════════════════════ # PUT /users/{user_id} — 更新 # ═══════════════════════════════════════════════════════════════ class TestUpdateUser: async def test_update_display_name( self, client: AsyncClient, admin_user, admin_token ): """更新 display_name。""" resp = await client.put( f"/api/v1/users/{admin_user.id}", headers=auth_header(admin_token), json={"display_name": "管理员改名"}, ) assert resp.status_code == 200 assert resp.json()["display_name"] == "管理员改名" async def test_update_email( self, client: AsyncClient, admin_user, admin_token ): """更新 email。""" resp = await client.put( f"/api/v1/users/{admin_user.id}", headers=auth_header(admin_token), json={"email": "admin@example.com"}, ) assert resp.status_code == 200 assert resp.json()["email"] == "admin@example.com" async def test_update_role( self, client: AsyncClient, admin_user, dba_user, admin_token ): """更新用户角色。""" resp = await client.put( f"/api/v1/users/{dba_user.id}", headers=auth_header(admin_token), json={"role": "developer"}, ) assert resp.status_code == 200 assert resp.json()["role"] == "developer" async def test_update_is_active( self, client: AsyncClient, dba_user, admin_token ): """禁用用户。""" resp = await client.put( f"/api/v1/users/{dba_user.id}", headers=auth_header(admin_token), json={"is_active": False}, ) assert resp.status_code == 200 assert resp.json()["is_active"] is False async def test_update_multiple_fields( self, client: AsyncClient, dba_user, admin_token ): """同时更新多个字段。""" resp = await client.put( f"/api/v1/users/{dba_user.id}", headers=auth_header(admin_token), json={ "display_name": "DBA改名", "phone": "13900139000", }, ) assert resp.status_code == 200 data = resp.json() assert data["display_name"] == "DBA改名" assert data["phone"] == "13900139000" async def test_update_nonexistent_user(self, client: AsyncClient, admin_token): """更新不存在的用户返回 404。""" resp = await client.put( "/api/v1/users/9999", headers=auth_header(admin_token), json={"display_name": "不存在"}, ) assert resp.status_code == 404 async def test_update_invalid_role( self, client: AsyncClient, admin_user, admin_token ): """无效角色返回 422。""" resp = await client.put( f"/api/v1/users/{admin_user.id}", headers=auth_header(admin_token), json={"role": "superadmin"}, ) assert resp.status_code == 422 async def test_update_non_admin_forbidden( self, client: AsyncClient, admin_user, developer_token ): """developer 角色无权更新用户。""" resp = await client.put( f"/api/v1/users/{admin_user.id}", headers=auth_header(developer_token), json={"display_name": "越权"}, ) assert resp.status_code == 403 # ═══════════════════════════════════════════════════════════════ # DELETE /users/{user_id} — 删除 # ═══════════════════════════════════════════════════════════════ class TestDeleteUser: async def test_delete_user_success( self, client: AsyncClient, dba_user, admin_token ): """成功删除用户。""" resp = await client.delete( f"/api/v1/users/{dba_user.id}", headers=auth_header(admin_token), ) assert resp.status_code == 200 assert "已删除" in resp.json()["message"] # 确认已被删除 resp2 = await client.get( f"/api/v1/users/{dba_user.id}", headers=auth_header(admin_token), ) assert resp2.status_code == 404 async def test_delete_self( self, client: AsyncClient, admin_user, admin_token ): """不能删除自己。""" resp = await client.delete( f"/api/v1/users/{admin_user.id}", headers=auth_header(admin_token), ) assert resp.status_code == 400 assert "不能删除自己" in resp.json()["detail"] async def test_delete_nonexistent_user(self, client: AsyncClient, admin_token): """删除不存在的用户返回 404。""" resp = await client.delete( "/api/v1/users/9999", headers=auth_header(admin_token), ) assert resp.status_code == 404 async def test_delete_non_admin_forbidden( self, client: AsyncClient, admin_user, developer_token ): """developer 角色无权删除用户。""" resp = await client.delete( f"/api/v1/users/{admin_user.id}", headers=auth_header(developer_token), ) assert resp.status_code == 403 async def test_delete_unauthenticated(self, client: AsyncClient, admin_user): """未认证请求返回 401。""" resp = await client.delete(f"/api/v1/users/{admin_user.id}") assert resp.status_code == 401 async def test_delete_guest_forbidden( self, client: AsyncClient, admin_user, guest_token ): """guest 角色无权删除用户。""" resp = await client.delete( f"/api/v1/users/{admin_user.id}", headers=auth_header(guest_token), ) assert resp.status_code == 403