| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254 |
- """OA 审批人解析单元测试"""
- import unittest
- from unittest.mock import MagicMock, patch
- from app.services.oa_assignee import (
- AssigneeResolveError,
- normalize_cc_assignee_types,
- resolve_assignees,
- resolve_cc_assignees,
- )
- class TestOaAssignee(unittest.TestCase):
- def test_users_resolves_active_ids(self):
- db = MagicMock()
- with patch(
- "app.services.oa_assignee._active_user_id",
- side_effect=lambda _db, uid, _tn: uid if uid else None,
- ):
- users, meta = resolve_assignees(
- db,
- tenant_no="zkxw",
- assignee_config={"assigneeType": "users", "userIds": ["u1", "u1", ""]},
- initiator_user_id="initiator",
- )
- self.assertEqual(users, ["u1"])
- self.assertEqual(meta.get("resolve_source"), "users")
- def test_dept_leader_resolves_by_initiator_dept(self):
- db = MagicMock()
- dept = MagicMock(leader_user_id="leaderA")
- db.query.return_value.filter.return_value.first.return_value = dept
- with patch(
- "app.services.oa_assignee._active_user_id",
- side_effect=lambda _db, uid, _tn: uid if uid else None,
- ):
- users, meta = resolve_assignees(
- db,
- tenant_no="zkxw",
- assignee_config={"assigneeType": "dept_leader"},
- initiator_user_id="zhang",
- initiator_dept_id="deptA",
- )
- self.assertEqual(users, ["leaderA"])
- self.assertEqual(meta.get("resolve_source"), "dept_leader")
- def test_direct_supervisor_fallback_to_dept_leader(self):
- db = MagicMock()
- initiator = MagicMock(supervisor_user_id=None, dept_id="deptA")
- dept = MagicMock(leader_user_id="leaderA")
- db.query.return_value.filter.return_value.first.side_effect = [initiator, dept]
- with patch(
- "app.services.oa_assignee._active_user_id",
- side_effect=lambda _db, uid, _tn: uid if uid else None,
- ):
- users, meta = resolve_assignees(
- db,
- tenant_no="zkxw",
- assignee_config={"assigneeType": "direct_supervisor"},
- initiator_user_id="no_supervisor",
- initiator_dept_id="deptA",
- advanced={"supervisorFallbackToDeptLeader": True},
- )
- self.assertEqual(users, ["leaderA"])
- self.assertEqual(meta.get("resolve_source"), "supervisor_fallback_dept_leader")
- def test_direct_supervisor_allows_self(self):
- """部门经理等场景:直属主管可配置为本人,解析应成功。"""
- db = MagicMock()
- initiator = MagicMock(supervisor_user_id="manager_self", dept_id="deptA")
- db.query.return_value.filter.return_value.first.return_value = initiator
- with patch(
- "app.services.oa_assignee._active_user_id",
- side_effect=lambda _db, uid, _tn: uid if uid else None,
- ):
- users, meta = resolve_assignees(
- db,
- tenant_no="zkxw",
- assignee_config={"assigneeType": "direct_supervisor"},
- initiator_user_id="manager_self",
- initiator_dept_id="deptA",
- )
- self.assertEqual(users, ["manager_self"])
- self.assertEqual(meta.get("resolve_source"), "direct_supervisor")
- def test_direct_supervisor_no_fallback_raises(self):
- db = MagicMock()
- initiator = MagicMock(supervisor_user_id=None, dept_id="deptA")
- db.query.return_value.filter.return_value.first.return_value = initiator
- with patch(
- "app.services.oa_assignee._active_user_id",
- return_value=None,
- ):
- with self.assertRaises(AssigneeResolveError):
- resolve_assignees(
- db,
- tenant_no="zkxw",
- assignee_config={"assigneeType": "direct_supervisor"},
- initiator_user_id="no_supervisor",
- initiator_dept_id="deptA",
- advanced={"supervisorFallbackToDeptLeader": False},
- )
- def test_posts_resolve(self):
- db = MagicMock()
- with patch(
- "app.services.oa_assignee.user_crud.list_user_ids_bound_to_posts",
- return_value=["gm_user", "inactive"],
- ), patch(
- "app.services.oa_assignee._active_user_id",
- side_effect=lambda _db, uid, _tn: uid if uid == "gm_user" else None,
- ):
- users, meta = resolve_assignees(
- db,
- tenant_no="zkxw",
- assignee_config={"assigneeType": "posts", "postIds": ["post_gm"]},
- initiator_user_id="zhang",
- )
- self.assertEqual(users, ["gm_user"])
- self.assertEqual(meta.get("resolve_source"), "posts")
- def test_never_reads_sys_role(self):
- db = MagicMock()
- with patch(
- "app.services.oa_assignee.user_crud.list_user_ids_bound_to_posts",
- return_value=["post_only"],
- ) as mock_list, patch(
- "app.services.oa_assignee._active_user_id",
- side_effect=lambda _db, uid, _tn: uid,
- ):
- users, _meta = resolve_assignees(
- db,
- tenant_no="zkxw",
- assignee_config={"assigneeType": "posts", "postIds": ["p1"]},
- initiator_user_id="zhang",
- )
- self.assertEqual(users, ["post_only"])
- mock_list.assert_called_once()
- for call in db.query.call_args_list:
- args = call[0] if call[0] else ()
- for a in args:
- name = getattr(a, "__name__", None) or getattr(a, "__tablename__", None) or str(a)
- self.assertNotIn("SysRole", str(name))
- self.assertNotIn("sys_role", str(name))
- def test_initiator_select_empty_raises(self):
- db = MagicMock()
- with self.assertRaises(AssigneeResolveError):
- resolve_assignees(
- db,
- tenant_no="zkxw",
- assignee_config={"assigneeType": "initiator_select"},
- initiator_user_id="zhang",
- initiator_select_user_ids=[],
- )
- def test_normalize_prefers_assignee_types(self):
- self.assertEqual(
- normalize_cc_assignee_types(
- {"assigneeTypes": ["users", "dept_leader", "users", "bad"], "assigneeType": "posts"}
- ),
- ["users", "dept_leader"],
- )
- def test_normalize_fallback_single(self):
- self.assertEqual(
- normalize_cc_assignee_types({"assigneeType": "initiator_select"}),
- ["initiator_select"],
- )
- self.assertEqual(normalize_cc_assignee_types({}), [])
- def test_resolve_cc_union_dedupe(self):
- db = MagicMock()
- with patch(
- "app.services.oa_assignee.resolve_assignees",
- side_effect=[
- (["u1", "u2"], {"resolve_source": "users"}),
- (["u2", "u3"], {"resolve_source": "dept_leader"}),
- ],
- ) as mock_resolve:
- users, meta = resolve_cc_assignees(
- db,
- tenant_no="zkxw",
- assignee_config={
- "assigneeTypes": ["users", "dept_leader"],
- "userIds": ["u1", "u2"],
- },
- initiator_user_id="zhang",
- initiator_dept_id="d1",
- )
- self.assertEqual(users, ["u1", "u2", "u3"])
- self.assertEqual(meta.get("resolve_source"), "multi")
- self.assertEqual(meta.get("sources"), ["users", "dept_leader"])
- self.assertEqual(mock_resolve.call_count, 2)
- def test_resolve_cc_skips_failed_strategy(self):
- db = MagicMock()
- with patch(
- "app.services.oa_assignee.resolve_assignees",
- side_effect=[
- AssigneeResolveError("部门负责人未配置"),
- (["u1"], {"resolve_source": "users"}),
- ],
- ):
- users, meta = resolve_cc_assignees(
- db,
- tenant_no="zkxw",
- assignee_config={
- "assigneeTypes": ["dept_leader", "users"],
- "userIds": ["u1"],
- },
- initiator_user_id="zhang",
- initiator_dept_id="d1",
- )
- self.assertEqual(users, ["u1"])
- self.assertEqual(meta.get("resolve_source"), "users")
- self.assertEqual(len(meta.get("skipped") or []), 1)
- def test_resolve_cc_all_fail_returns_empty(self):
- db = MagicMock()
- with patch(
- "app.services.oa_assignee.resolve_assignees",
- side_effect=AssigneeResolveError("fail"),
- ):
- users, meta = resolve_cc_assignees(
- db,
- tenant_no="zkxw",
- assignee_config={"assigneeTypes": ["dept_leader"]},
- initiator_user_id="zhang",
- initiator_dept_id="d1",
- )
- self.assertEqual(users, [])
- self.assertTrue(meta.get("skipped"))
- def test_resolve_cc_legacy_single_type(self):
- db = MagicMock()
- with patch(
- "app.services.oa_assignee.resolve_assignees",
- return_value=(["u1"], {"resolve_source": "users"}),
- ) as mock_resolve:
- users, meta = resolve_cc_assignees(
- db,
- tenant_no="zkxw",
- assignee_config={"assigneeType": "users", "userIds": ["u1"]},
- initiator_user_id="zhang",
- )
- self.assertEqual(users, ["u1"])
- self.assertEqual(meta.get("resolve_source"), "users")
- single_cfg = mock_resolve.call_args.kwargs["assignee_config"]
- self.assertEqual(single_cfg.get("assigneeType"), "users")
- if __name__ == "__main__":
- unittest.main()
|