test_oa_assignee.py 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236
  1. """OA 审批人解析单元测试"""
  2. import unittest
  3. from unittest.mock import MagicMock, patch
  4. from app.services.oa_assignee import (
  5. AssigneeResolveError,
  6. normalize_cc_assignee_types,
  7. resolve_assignees,
  8. resolve_cc_assignees,
  9. )
  10. class TestOaAssignee(unittest.TestCase):
  11. def test_users_resolves_active_ids(self):
  12. db = MagicMock()
  13. with patch(
  14. "app.services.oa_assignee._active_user_id",
  15. side_effect=lambda _db, uid, _tn: uid if uid else None,
  16. ):
  17. users, meta = resolve_assignees(
  18. db,
  19. tenant_no="zkxw",
  20. assignee_config={"assigneeType": "users", "userIds": ["u1", "u1", ""]},
  21. initiator_user_id="initiator",
  22. )
  23. self.assertEqual(users, ["u1"])
  24. self.assertEqual(meta.get("resolve_source"), "users")
  25. def test_dept_leader_resolves_by_initiator_dept(self):
  26. db = MagicMock()
  27. dept = MagicMock(leader_user_id="leaderA")
  28. db.query.return_value.filter.return_value.first.return_value = dept
  29. with patch(
  30. "app.services.oa_assignee._active_user_id",
  31. side_effect=lambda _db, uid, _tn: uid if uid else None,
  32. ):
  33. users, meta = resolve_assignees(
  34. db,
  35. tenant_no="zkxw",
  36. assignee_config={"assigneeType": "dept_leader"},
  37. initiator_user_id="zhang",
  38. initiator_dept_id="deptA",
  39. )
  40. self.assertEqual(users, ["leaderA"])
  41. self.assertEqual(meta.get("resolve_source"), "dept_leader")
  42. def test_direct_supervisor_fallback_to_dept_leader(self):
  43. db = MagicMock()
  44. initiator = MagicMock(supervisor_user_id=None, dept_id="deptA")
  45. dept = MagicMock(leader_user_id="leaderA")
  46. db.query.return_value.filter.return_value.first.side_effect = [initiator, dept]
  47. with patch(
  48. "app.services.oa_assignee._active_user_id",
  49. side_effect=lambda _db, uid, _tn: uid if uid else None,
  50. ):
  51. users, meta = resolve_assignees(
  52. db,
  53. tenant_no="zkxw",
  54. assignee_config={"assigneeType": "direct_supervisor"},
  55. initiator_user_id="no_supervisor",
  56. initiator_dept_id="deptA",
  57. advanced={"supervisorFallbackToDeptLeader": True},
  58. )
  59. self.assertEqual(users, ["leaderA"])
  60. self.assertEqual(meta.get("resolve_source"), "supervisor_fallback_dept_leader")
  61. def test_direct_supervisor_no_fallback_raises(self):
  62. db = MagicMock()
  63. initiator = MagicMock(supervisor_user_id=None, dept_id="deptA")
  64. db.query.return_value.filter.return_value.first.return_value = initiator
  65. with patch(
  66. "app.services.oa_assignee._active_user_id",
  67. return_value=None,
  68. ):
  69. with self.assertRaises(AssigneeResolveError):
  70. resolve_assignees(
  71. db,
  72. tenant_no="zkxw",
  73. assignee_config={"assigneeType": "direct_supervisor"},
  74. initiator_user_id="no_supervisor",
  75. initiator_dept_id="deptA",
  76. advanced={"supervisorFallbackToDeptLeader": False},
  77. )
  78. def test_oa_role_gm(self):
  79. db = MagicMock()
  80. with patch(
  81. "app.services.oa_assignee.oa_crud.list_oa_role_user_ids",
  82. return_value=["gm_user", "inactive"],
  83. ), patch(
  84. "app.services.oa_assignee._active_user_id",
  85. side_effect=lambda _db, uid, _tn: uid if uid == "gm_user" else None,
  86. ):
  87. users, meta = resolve_assignees(
  88. db,
  89. tenant_no="zkxw",
  90. assignee_config={"assigneeType": "oa_roles", "oaRoleIds": ["gm_role_id"]},
  91. initiator_user_id="zhang",
  92. )
  93. self.assertEqual(users, ["gm_user"])
  94. self.assertEqual(meta.get("resolve_source"), "oa_roles")
  95. def test_never_reads_sys_role(self):
  96. db = MagicMock()
  97. with patch(
  98. "app.services.oa_assignee.oa_crud.list_oa_role_user_ids",
  99. return_value=["oa_only"],
  100. ) as mock_list, patch(
  101. "app.services.oa_assignee._active_user_id",
  102. side_effect=lambda _db, uid, _tn: uid,
  103. ):
  104. users, _meta = resolve_assignees(
  105. db,
  106. tenant_no="zkxw",
  107. assignee_config={"assigneeType": "oa_roles", "oaRoleIds": ["r1"]},
  108. initiator_user_id="zhang",
  109. )
  110. self.assertEqual(users, ["oa_only"])
  111. mock_list.assert_called_once()
  112. # 业务路径不得查询 sys_role
  113. for call in db.query.call_args_list:
  114. args = call[0] if call[0] else ()
  115. for a in args:
  116. name = getattr(a, "__name__", None) or getattr(a, "__tablename__", None) or str(a)
  117. self.assertNotIn("SysRole", str(name))
  118. self.assertNotIn("sys_role", str(name))
  119. def test_initiator_select_empty_raises(self):
  120. db = MagicMock()
  121. with self.assertRaises(AssigneeResolveError):
  122. resolve_assignees(
  123. db,
  124. tenant_no="zkxw",
  125. assignee_config={"assigneeType": "initiator_select"},
  126. initiator_user_id="zhang",
  127. initiator_select_user_ids=[],
  128. )
  129. def test_normalize_prefers_assignee_types(self):
  130. self.assertEqual(
  131. normalize_cc_assignee_types(
  132. {"assigneeTypes": ["users", "dept_leader", "users", "bad"], "assigneeType": "oa_roles"}
  133. ),
  134. ["users", "dept_leader"],
  135. )
  136. def test_normalize_fallback_single(self):
  137. self.assertEqual(
  138. normalize_cc_assignee_types({"assigneeType": "initiator_select"}),
  139. ["initiator_select"],
  140. )
  141. self.assertEqual(normalize_cc_assignee_types({}), [])
  142. def test_resolve_cc_union_dedupe(self):
  143. db = MagicMock()
  144. with patch(
  145. "app.services.oa_assignee.resolve_assignees",
  146. side_effect=[
  147. (["u1", "u2"], {"resolve_source": "users"}),
  148. (["u2", "u3"], {"resolve_source": "dept_leader"}),
  149. ],
  150. ) as mock_resolve:
  151. users, meta = resolve_cc_assignees(
  152. db,
  153. tenant_no="zkxw",
  154. assignee_config={
  155. "assigneeTypes": ["users", "dept_leader"],
  156. "userIds": ["u1", "u2"],
  157. },
  158. initiator_user_id="zhang",
  159. initiator_dept_id="d1",
  160. )
  161. self.assertEqual(users, ["u1", "u2", "u3"])
  162. self.assertEqual(meta.get("resolve_source"), "multi")
  163. self.assertEqual(meta.get("sources"), ["users", "dept_leader"])
  164. self.assertEqual(mock_resolve.call_count, 2)
  165. def test_resolve_cc_skips_failed_strategy(self):
  166. db = MagicMock()
  167. with patch(
  168. "app.services.oa_assignee.resolve_assignees",
  169. side_effect=[
  170. AssigneeResolveError("部门负责人未配置"),
  171. (["u1"], {"resolve_source": "users"}),
  172. ],
  173. ):
  174. users, meta = resolve_cc_assignees(
  175. db,
  176. tenant_no="zkxw",
  177. assignee_config={
  178. "assigneeTypes": ["dept_leader", "users"],
  179. "userIds": ["u1"],
  180. },
  181. initiator_user_id="zhang",
  182. initiator_dept_id="d1",
  183. )
  184. self.assertEqual(users, ["u1"])
  185. self.assertEqual(meta.get("resolve_source"), "users")
  186. self.assertEqual(len(meta.get("skipped") or []), 1)
  187. def test_resolve_cc_all_fail_returns_empty(self):
  188. db = MagicMock()
  189. with patch(
  190. "app.services.oa_assignee.resolve_assignees",
  191. side_effect=AssigneeResolveError("fail"),
  192. ):
  193. users, meta = resolve_cc_assignees(
  194. db,
  195. tenant_no="zkxw",
  196. assignee_config={"assigneeTypes": ["dept_leader"]},
  197. initiator_user_id="zhang",
  198. initiator_dept_id="d1",
  199. )
  200. self.assertEqual(users, [])
  201. self.assertTrue(meta.get("skipped"))
  202. def test_resolve_cc_legacy_single_type(self):
  203. db = MagicMock()
  204. with patch(
  205. "app.services.oa_assignee.resolve_assignees",
  206. return_value=(["u1"], {"resolve_source": "users"}),
  207. ) as mock_resolve:
  208. users, meta = resolve_cc_assignees(
  209. db,
  210. tenant_no="zkxw",
  211. assignee_config={"assigneeType": "users", "userIds": ["u1"]},
  212. initiator_user_id="zhang",
  213. )
  214. self.assertEqual(users, ["u1"])
  215. self.assertEqual(meta.get("resolve_source"), "users")
  216. single_cfg = mock_resolve.call_args.kwargs["assignee_config"]
  217. self.assertEqual(single_cfg.get("assigneeType"), "users")
  218. if __name__ == "__main__":
  219. unittest.main()