test_reorder_directory.py 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259
  1. """契约1.27 机构/用户/角色列表拖动重排测试。
  2. 覆盖:
  3. - 机构:同级槽位置换(步长10重编号 sort_no)、范围拒绝、父级不存在、权限
  4. - 用户:全体槽位置换、列表顺序联动、重复提交幂等、无效ID、权限
  5. - 角色:创建后槽位置换、无效ID、权限
  6. - 审计:REORDER_ORGANIZATION / REORDER_USER / REORDER_ROLE 落库与明细
  7. """
  8. from __future__ import annotations
  9. from dms.extensions import db
  10. from dms.models import AuditLog, Organization, Role, User
  11. def _auth(token: str) -> dict[str, str]:
  12. return {"Authorization": f"Bearer {token}"}
  13. def _org_id(app, code: str) -> int:
  14. with app.app_context():
  15. row = db.session.scalar(
  16. db.select(Organization).where(Organization.org_code == code)
  17. )
  18. assert row is not None, f"organization {code} not found"
  19. return row.id
  20. def _user_id(app, username: str) -> int:
  21. with app.app_context():
  22. row = db.session.scalar(
  23. db.select(User).where(User.username == username)
  24. )
  25. assert row is not None, f"user {username} not found"
  26. return row.id
  27. def _create_role(client, token: str, name: str, sort_no: int = 100):
  28. return client.post(
  29. "/api/v1/roles",
  30. json={
  31. "roleName": name,
  32. "sortNo": sort_no,
  33. "status": "ENABLED",
  34. "remark": None,
  35. "isDefault": False,
  36. },
  37. headers=_auth(token),
  38. )
  39. # ===== 机构 =====
  40. def test_reorder_organizations_within_parent(b3_app, b3_client, login_token):
  41. root_id = _org_id(b3_app, "ORG_ROOT")
  42. ops_id = _org_id(b3_app, "ORG_OPS")
  43. comms_id = _org_id(b3_app, "ORG_COMMS")
  44. response = b3_client.put(
  45. "/api/v1/organizations/reorder",
  46. json={
  47. "parentId": str(root_id),
  48. "orderedIds": [str(comms_id), str(ops_id)],
  49. },
  50. headers=_auth(login_token),
  51. )
  52. assert response.status_code == 200
  53. # 槽位置换:原次序 OPS(20)/COMMS(30),置换后 COMMS 落槽位1(=10)、
  54. # OPS 落槽位2(=20,与原值相同),实际发生变化的仅 COMMS 一行。
  55. assert response.get_json()["data"] == {"reorderedCount": 1}
  56. with b3_app.app_context():
  57. ops = db.session.scalar(
  58. db.select(Organization).where(Organization.org_code == "ORG_OPS")
  59. )
  60. comms = db.session.scalar(
  61. db.select(Organization).where(Organization.org_code == "ORG_COMMS")
  62. )
  63. assert comms.sort_no == 10
  64. assert ops.sort_no == 20
  65. def test_reorder_organizations_rejects_cross_scope(b3_app, b3_client, login_token):
  66. ops_id = _org_id(b3_app, "ORG_OPS")
  67. comms_id = _org_id(b3_app, "ORG_COMMS")
  68. response = b3_client.put(
  69. "/api/v1/organizations/reorder",
  70. json={"parentId": None, "orderedIds": [str(ops_id), str(comms_id)]},
  71. headers=_auth(login_token),
  72. )
  73. assert response.status_code == 400
  74. def test_reorder_organizations_rejects_missing_parent(b3_client, login_token):
  75. response = b3_client.put(
  76. "/api/v1/organizations/reorder",
  77. json={"parentId": "999999", "orderedIds": ["1", "2"]},
  78. headers=_auth(login_token),
  79. )
  80. assert response.status_code == 404
  81. def test_reorder_organizations_requires_admin(b3_client, token_for):
  82. response = b3_client.put(
  83. "/api/v1/organizations/reorder",
  84. json={"parentId": None, "orderedIds": ["1", "2"]},
  85. headers=_auth(token_for("user")),
  86. )
  87. assert response.status_code == 403
  88. # ===== 用户 =====
  89. def test_reorder_users_slot_swap_and_idempotent(b3_app, b3_client, login_token):
  90. admin_id = _user_id(b3_app, "admin")
  91. user_id = _user_id(b3_app, "user")
  92. response = b3_client.put(
  93. "/api/v1/users/reorder",
  94. json={"orderedIds": [str(user_id), str(admin_id)]},
  95. headers=_auth(login_token),
  96. )
  97. assert response.status_code == 200
  98. # 种子用户 sort_no 全空:[admin, user, disabled] 置换为 [user, admin, disabled]
  99. assert response.get_json()["data"] == {"reorderedCount": 3}
  100. listing = b3_client.get("/api/v1/users", headers=_auth(login_token))
  101. assert listing.status_code == 200
  102. usernames = [
  103. item["username"] for item in listing.get_json()["data"]["items"]
  104. ]
  105. assert usernames[:3] == ["user", "admin", "disabled"]
  106. # 相同顺序重复提交:无变更
  107. repeat = b3_client.put(
  108. "/api/v1/users/reorder",
  109. json={"orderedIds": [str(user_id), str(admin_id)]},
  110. headers=_auth(login_token),
  111. )
  112. assert repeat.status_code == 200
  113. assert repeat.get_json()["data"] == {"reorderedCount": 0}
  114. def test_reorder_users_rejects_unknown_id(b3_client, login_token):
  115. response = b3_client.put(
  116. "/api/v1/users/reorder",
  117. json={"orderedIds": ["999999", "888888"]},
  118. headers=_auth(login_token),
  119. )
  120. assert response.status_code == 404
  121. def test_reorder_users_requires_admin(b3_client, token_for):
  122. response = b3_client.put(
  123. "/api/v1/users/reorder",
  124. json={"orderedIds": ["999999", "888888"]},
  125. headers=_auth(token_for("user")),
  126. )
  127. assert response.status_code == 403
  128. # ===== 角色 =====
  129. def test_reorder_roles_slot_swap(b2_app, b2_client, login_token, role_cleanup):
  130. first = _create_role(b2_client, login_token, "资料员", sort_no=10)
  131. second = _create_role(b2_client, login_token, "档案员", sort_no=20)
  132. assert first.status_code == 201 and second.status_code == 201
  133. first_id = first.get_json()["data"]["id"]
  134. second_id = second.get_json()["data"]["id"]
  135. response = b2_client.put(
  136. "/api/v1/roles/reorder",
  137. json={"orderedIds": [second_id, first_id]},
  138. headers=_auth(login_token),
  139. )
  140. assert response.status_code == 200
  141. assert response.get_json()["data"] == {"reorderedCount": 2}
  142. with b2_app.app_context():
  143. rows = {
  144. row.role_name: row.sort_no
  145. for row in db.session.scalars(db.select(Role)).all()
  146. }
  147. assert rows == {"资料员": 20, "档案员": 10}
  148. def test_reorder_roles_rejects_unknown_id(
  149. b2_client, login_token, role_cleanup
  150. ):
  151. created = _create_role(b2_client, login_token, "资料员")
  152. assert created.status_code == 201
  153. response = b2_client.put(
  154. "/api/v1/roles/reorder",
  155. json={"orderedIds": ["999999", "888888"]},
  156. headers=_auth(login_token),
  157. )
  158. assert response.status_code == 400
  159. # ===== 审计 =====
  160. def test_reorder_writes_three_audit_actions(
  161. b3_app, b3_client, login_token, role_cleanup
  162. ):
  163. root_id = _org_id(b3_app, "ORG_ROOT")
  164. ops_id = _org_id(b3_app, "ORG_OPS")
  165. comms_id = _org_id(b3_app, "ORG_COMMS")
  166. org_response = b3_client.put(
  167. "/api/v1/organizations/reorder",
  168. json={
  169. "parentId": str(root_id),
  170. "orderedIds": [str(comms_id), str(ops_id)],
  171. },
  172. headers=_auth(login_token),
  173. )
  174. assert org_response.status_code == 200
  175. admin_id = _user_id(b3_app, "admin")
  176. user_id = _user_id(b3_app, "user")
  177. user_response = b3_client.put(
  178. "/api/v1/users/reorder",
  179. json={"orderedIds": [str(user_id), str(admin_id)]},
  180. headers=_auth(login_token),
  181. )
  182. assert user_response.status_code == 200
  183. first_role = _create_role(b3_client, login_token, "资料员")
  184. second_role = _create_role(b3_client, login_token, "档案员")
  185. assert first_role.status_code == 201 and second_role.status_code == 201
  186. role_response = b3_client.put(
  187. "/api/v1/roles/reorder",
  188. json={
  189. "orderedIds": [
  190. second_role.get_json()["data"]["id"],
  191. first_role.get_json()["data"]["id"],
  192. ]
  193. },
  194. headers=_auth(login_token),
  195. )
  196. assert role_response.status_code == 200
  197. with b3_app.app_context():
  198. rows = db.session.scalars(
  199. db.select(AuditLog).where(
  200. AuditLog.action_type.in_(
  201. [
  202. "REORDER_ORGANIZATION",
  203. "REORDER_USER",
  204. "REORDER_ROLE",
  205. ]
  206. )
  207. )
  208. ).all()
  209. by_action = {row.action_type: row for row in rows}
  210. assert set(by_action) == {
  211. "REORDER_ORGANIZATION",
  212. "REORDER_USER",
  213. "REORDER_ROLE",
  214. }
  215. assert by_action["REORDER_ORGANIZATION"].detail["reorderedCount"] == 1
  216. assert by_action["REORDER_USER"].detail["reorderedCount"] == 3
  217. assert by_action["REORDER_ROLE"].detail["reorderedCount"] == 2