test_oa_assignee.py 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235
  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_posts_resolve(self):
  79. db = MagicMock()
  80. with patch(
  81. "app.services.oa_assignee.user_crud.list_user_ids_bound_to_posts",
  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": "posts", "postIds": ["post_gm"]},
  91. initiator_user_id="zhang",
  92. )
  93. self.assertEqual(users, ["gm_user"])
  94. self.assertEqual(meta.get("resolve_source"), "posts")
  95. def test_never_reads_sys_role(self):
  96. db = MagicMock()
  97. with patch(
  98. "app.services.oa_assignee.user_crud.list_user_ids_bound_to_posts",
  99. return_value=["post_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": "posts", "postIds": ["p1"]},
  108. initiator_user_id="zhang",
  109. )
  110. self.assertEqual(users, ["post_only"])
  111. mock_list.assert_called_once()
  112. for call in db.query.call_args_list:
  113. args = call[0] if call[0] else ()
  114. for a in args:
  115. name = getattr(a, "__name__", None) or getattr(a, "__tablename__", None) or str(a)
  116. self.assertNotIn("SysRole", str(name))
  117. self.assertNotIn("sys_role", str(name))
  118. def test_initiator_select_empty_raises(self):
  119. db = MagicMock()
  120. with self.assertRaises(AssigneeResolveError):
  121. resolve_assignees(
  122. db,
  123. tenant_no="zkxw",
  124. assignee_config={"assigneeType": "initiator_select"},
  125. initiator_user_id="zhang",
  126. initiator_select_user_ids=[],
  127. )
  128. def test_normalize_prefers_assignee_types(self):
  129. self.assertEqual(
  130. normalize_cc_assignee_types(
  131. {"assigneeTypes": ["users", "dept_leader", "users", "bad"], "assigneeType": "posts"}
  132. ),
  133. ["users", "dept_leader"],
  134. )
  135. def test_normalize_fallback_single(self):
  136. self.assertEqual(
  137. normalize_cc_assignee_types({"assigneeType": "initiator_select"}),
  138. ["initiator_select"],
  139. )
  140. self.assertEqual(normalize_cc_assignee_types({}), [])
  141. def test_resolve_cc_union_dedupe(self):
  142. db = MagicMock()
  143. with patch(
  144. "app.services.oa_assignee.resolve_assignees",
  145. side_effect=[
  146. (["u1", "u2"], {"resolve_source": "users"}),
  147. (["u2", "u3"], {"resolve_source": "dept_leader"}),
  148. ],
  149. ) as mock_resolve:
  150. users, meta = resolve_cc_assignees(
  151. db,
  152. tenant_no="zkxw",
  153. assignee_config={
  154. "assigneeTypes": ["users", "dept_leader"],
  155. "userIds": ["u1", "u2"],
  156. },
  157. initiator_user_id="zhang",
  158. initiator_dept_id="d1",
  159. )
  160. self.assertEqual(users, ["u1", "u2", "u3"])
  161. self.assertEqual(meta.get("resolve_source"), "multi")
  162. self.assertEqual(meta.get("sources"), ["users", "dept_leader"])
  163. self.assertEqual(mock_resolve.call_count, 2)
  164. def test_resolve_cc_skips_failed_strategy(self):
  165. db = MagicMock()
  166. with patch(
  167. "app.services.oa_assignee.resolve_assignees",
  168. side_effect=[
  169. AssigneeResolveError("部门负责人未配置"),
  170. (["u1"], {"resolve_source": "users"}),
  171. ],
  172. ):
  173. users, meta = resolve_cc_assignees(
  174. db,
  175. tenant_no="zkxw",
  176. assignee_config={
  177. "assigneeTypes": ["dept_leader", "users"],
  178. "userIds": ["u1"],
  179. },
  180. initiator_user_id="zhang",
  181. initiator_dept_id="d1",
  182. )
  183. self.assertEqual(users, ["u1"])
  184. self.assertEqual(meta.get("resolve_source"), "users")
  185. self.assertEqual(len(meta.get("skipped") or []), 1)
  186. def test_resolve_cc_all_fail_returns_empty(self):
  187. db = MagicMock()
  188. with patch(
  189. "app.services.oa_assignee.resolve_assignees",
  190. side_effect=AssigneeResolveError("fail"),
  191. ):
  192. users, meta = resolve_cc_assignees(
  193. db,
  194. tenant_no="zkxw",
  195. assignee_config={"assigneeTypes": ["dept_leader"]},
  196. initiator_user_id="zhang",
  197. initiator_dept_id="d1",
  198. )
  199. self.assertEqual(users, [])
  200. self.assertTrue(meta.get("skipped"))
  201. def test_resolve_cc_legacy_single_type(self):
  202. db = MagicMock()
  203. with patch(
  204. "app.services.oa_assignee.resolve_assignees",
  205. return_value=(["u1"], {"resolve_source": "users"}),
  206. ) as mock_resolve:
  207. users, meta = resolve_cc_assignees(
  208. db,
  209. tenant_no="zkxw",
  210. assignee_config={"assigneeType": "users", "userIds": ["u1"]},
  211. initiator_user_id="zhang",
  212. )
  213. self.assertEqual(users, ["u1"])
  214. self.assertEqual(meta.get("resolve_source"), "users")
  215. single_cfg = mock_resolve.call_args.kwargs["assignee_config"]
  216. self.assertEqual(single_cfg.get("assigneeType"), "users")
  217. if __name__ == "__main__":
  218. unittest.main()