test_role_menus.py 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309
  1. """用户管理 / 角色授权 / 操作审计 API 测试"""
  2. from app.core.database import get_db
  3. from app.core.security import hash_password
  4. from app.crud import user as user_crud
  5. from app.main import app as fastapi_app
  6. def _login(client, username: str, password: str) -> dict:
  7. r = client.post(
  8. "/api/web/auth/login", json={"username": username, "password": password}
  9. )
  10. assert r.status_code == 200, r.text
  11. token = r.json()["data"]["token"]
  12. return {"Authorization": f"Bearer {token}"}
  13. def _client_db():
  14. gen = fastapi_app.dependency_overrides[get_db]()
  15. return next(gen)
  16. def _ensure_role_user(client, username: str, role_code: str, password: str = "Pass@123"):
  17. db = _client_db()
  18. role = user_crud.get_role_by_code(db, role_code)
  19. assert role is not None
  20. existing = user_crud.get_user_by_username(db, username)
  21. if not existing:
  22. user_crud.create_user(
  23. db,
  24. username=username,
  25. password_hash=hash_password(password),
  26. display_name=username,
  27. role_id=role.id,
  28. )
  29. return _login(client, username, password)
  30. def test_role_grant_updates_menus(client):
  31. admin_headers = _login(client, "admin", "Admin@123")
  32. roles = client.get("/api/web/admin/roles", headers=admin_headers).json()["data"]
  33. viewer = next(r for r in roles if r["roleCode"] == "viewer")
  34. menus_before = client.get(
  35. f"/api/web/admin/roles/{viewer['id']}/menus", headers=admin_headers
  36. ).json()["data"]
  37. menu_ids = list(menus_before["menuIds"])
  38. assert len(menu_ids) >= 1
  39. tree = client.get("/api/web/admin/menus/tree", headers=admin_headers).json()["data"]
  40. def _flatten(nodes):
  41. out = []
  42. for n in nodes or []:
  43. out.append(n)
  44. out.extend(_flatten(n.get("children") or []))
  45. return out
  46. flat = _flatten(tree)
  47. suggestion = next(m for m in flat if m["menuCode"] == "suggestion")
  48. new_ids = [mid for mid in menu_ids if mid != suggestion["id"]]
  49. assert suggestion["id"] in menu_ids
  50. put = client.put(
  51. f"/api/web/admin/roles/{viewer['id']}/menus",
  52. headers=admin_headers,
  53. json={"menuIds": new_ids},
  54. )
  55. assert put.status_code == 200, put.text
  56. viewer_headers = _ensure_role_user(client, "viewer_grant", "viewer")
  57. menus = client.get("/api/web/auth/menus", headers=viewer_headers).json()["data"]
  58. tree_codes = {m["menuCode"] for m in _flatten(menus["tree"])}
  59. assert "suggestion" not in tree_codes
  60. assert "dashboard" in tree_codes
  61. assert "feedback" in tree_codes
  62. def test_user_manage_crud(client):
  63. admin_headers = _login(client, "admin", "Admin@123")
  64. roles = client.get("/api/web/admin/roles", headers=admin_headers).json()["data"]
  65. handler = next(r for r in roles if r["roleCode"] == "handler")
  66. created = client.post(
  67. "/api/web/admin/users",
  68. headers=admin_headers,
  69. json={
  70. "username": "u_task7",
  71. "password": "Pass@123",
  72. "displayName": "任务七用户",
  73. "roleId": handler["id"],
  74. },
  75. )
  76. assert created.status_code == 200, created.text
  77. user = created.json()["data"]
  78. assert user["username"] == "u_task7"
  79. assert user["displayName"] == "任务七用户"
  80. assert user["roleId"] == handler["id"]
  81. listed = client.get("/api/web/admin/users", headers=admin_headers)
  82. assert listed.status_code == 200
  83. usernames = {u["username"] for u in listed.json()["data"]}
  84. assert "u_task7" in usernames
  85. updated = client.put(
  86. f"/api/web/admin/users/{user['id']}",
  87. headers=admin_headers,
  88. json={"displayName": "已改名", "status": "1"},
  89. )
  90. assert updated.status_code == 200
  91. assert updated.json()["data"]["displayName"] == "已改名"
  92. assert updated.json()["data"]["status"] == "1"
  93. reset = client.put(
  94. f"/api/web/admin/users/{user['id']}/reset-password",
  95. headers=admin_headers,
  96. json={"password": "NewPass@123"},
  97. )
  98. assert reset.status_code == 200
  99. # 启停后不可登录;先启再用新密码登录
  100. client.put(
  101. f"/api/web/admin/users/{user['id']}",
  102. headers=admin_headers,
  103. json={"status": "0"},
  104. )
  105. login = client.post(
  106. "/api/web/auth/login",
  107. json={"username": "u_task7", "password": "NewPass@123"},
  108. )
  109. assert login.status_code == 200
  110. def test_oper_logs_list_requires_audit(client):
  111. admin_headers = _login(client, "admin", "Admin@123")
  112. r = client.get(
  113. "/api/web/admin/oper-logs",
  114. headers=admin_headers,
  115. params={"page": 1, "pageSize": 10},
  116. )
  117. assert r.status_code == 200
  118. data = r.json()["data"]
  119. assert "list" in data
  120. assert "total" in data
  121. assert data["page"] == 1
  122. assert data["pageSize"] == 10
  123. assert data["total"] >= 1 # login 已写日志
  124. viewer_headers = _ensure_role_user(client, "viewer_audit", "viewer")
  125. denied = client.get(
  126. "/api/web/admin/oper-logs",
  127. headers=viewer_headers,
  128. params={"page": 1, "pageSize": 10},
  129. )
  130. assert denied.status_code == 403
  131. def test_oper_logs_filter_by_username_and_keyword(client):
  132. from app.crud import oper_log as oper_log_crud
  133. db = _client_db()
  134. oper_log_crud.create_oper_log(
  135. db,
  136. module="用户管理",
  137. action="创建",
  138. username="filter_alice",
  139. detail="创建用户 alice_unique_detail",
  140. )
  141. oper_log_crud.create_oper_log(
  142. db,
  143. module="角色管理",
  144. action="更新",
  145. username="filter_bob",
  146. detail="更新角色 bob_other_detail",
  147. )
  148. admin_headers = _login(client, "admin", "Admin@123")
  149. by_user = client.get(
  150. "/api/web/admin/oper-logs",
  151. headers=admin_headers,
  152. params={"username": "filter_alice", "page": 1, "pageSize": 10},
  153. )
  154. assert by_user.status_code == 200
  155. user_data = by_user.json()["data"]
  156. assert user_data["total"] >= 1
  157. assert all("filter_alice" in (row["username"] or "") for row in user_data["list"])
  158. by_keyword = client.get(
  159. "/api/web/admin/oper-logs",
  160. headers=admin_headers,
  161. params={"keyword": "alice_unique_detail", "page": 1, "pageSize": 10},
  162. )
  163. assert by_keyword.status_code == 200
  164. kw_data = by_keyword.json()["data"]
  165. assert kw_data["total"] >= 1
  166. assert all(
  167. "alice_unique_detail" in (row["detail"] or "") for row in kw_data["list"]
  168. )
  169. assert not any(
  170. "bob_other_detail" in (row["detail"] or "") for row in kw_data["list"]
  171. )
  172. def test_role_create_and_update(client):
  173. admin_headers = _login(client, "admin", "Admin@123")
  174. created = client.post(
  175. "/api/web/admin/roles",
  176. headers=admin_headers,
  177. json={
  178. "roleCode": "custom_role",
  179. "roleName": "自定义角色",
  180. "remark": "备注A",
  181. },
  182. )
  183. assert created.status_code == 200, created.text
  184. role = created.json()["data"]
  185. assert role["roleCode"] == "custom_role"
  186. assert role["roleName"] == "自定义角色"
  187. assert role["remark"] == "备注A"
  188. assert role["id"]
  189. dup = client.post(
  190. "/api/web/admin/roles",
  191. headers=admin_headers,
  192. json={"roleCode": "custom_role", "roleName": "重复编码"},
  193. )
  194. assert dup.status_code == 400
  195. updated = client.put(
  196. f"/api/web/admin/roles/{role['id']}",
  197. headers=admin_headers,
  198. json={"roleName": "已改名", "remark": "备注B"},
  199. )
  200. assert updated.status_code == 200, updated.text
  201. data = updated.json()["data"]
  202. assert data["roleCode"] == "custom_role"
  203. assert data["roleName"] == "已改名"
  204. assert data["remark"] == "备注B"
  205. listed = client.get("/api/web/admin/roles", headers=admin_headers).json()["data"]
  206. found = next(r for r in listed if r["id"] == role["id"])
  207. assert found["roleName"] == "已改名"
  208. def test_role_delete(client):
  209. admin_headers = _login(client, "admin", "Admin@123")
  210. created = client.post(
  211. "/api/web/admin/roles",
  212. headers=admin_headers,
  213. json={"roleCode": "to_delete", "roleName": "待删角色"},
  214. )
  215. assert created.status_code == 200, created.text
  216. role = created.json()["data"]
  217. # 授权后再删,菜单关联应一并清理
  218. tree = client.get("/api/web/admin/menus/tree", headers=admin_headers).json()["data"]
  219. def _collect(nodes):
  220. ids = []
  221. for n in nodes:
  222. ids.append(n["id"])
  223. ids.extend(_collect(n.get("children") or []))
  224. return ids
  225. menu_ids = _collect(tree)[:1]
  226. assert menu_ids
  227. grant = client.put(
  228. f"/api/web/admin/roles/{role['id']}/menus",
  229. headers=admin_headers,
  230. json={"menuIds": menu_ids},
  231. )
  232. assert grant.status_code == 200, grant.text
  233. deleted = client.delete(
  234. f"/api/web/admin/roles/{role['id']}", headers=admin_headers
  235. )
  236. assert deleted.status_code == 200, deleted.text
  237. listed = client.get("/api/web/admin/roles", headers=admin_headers).json()["data"]
  238. assert all(r["id"] != role["id"] for r in listed)
  239. # 再次删除应报不存在
  240. again = client.delete(
  241. f"/api/web/admin/roles/{role['id']}", headers=admin_headers
  242. )
  243. assert again.status_code == 400
  244. def test_role_delete_rejects_admin_and_in_use(client):
  245. admin_headers = _login(client, "admin", "Admin@123")
  246. roles = client.get("/api/web/admin/roles", headers=admin_headers).json()["data"]
  247. admin_role = next(r for r in roles if r["roleCode"] == "admin")
  248. viewer = next(r for r in roles if r["roleCode"] == "viewer")
  249. deny_admin = client.delete(
  250. f"/api/web/admin/roles/{admin_role['id']}", headers=admin_headers
  251. )
  252. assert deny_admin.status_code == 400
  253. # viewer 已被测试用户占用时不可删
  254. _ensure_role_user(client, "viewer_keep", "viewer")
  255. deny_in_use = client.delete(
  256. f"/api/web/admin/roles/{viewer['id']}", headers=admin_headers
  257. )
  258. assert deny_in_use.status_code == 400
  259. assert "用户" in deny_in_use.json().get("message", "")