| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403 |
- """OA 发布校验单元测试"""
- import unittest
- from unittest.mock import MagicMock, patch
- from app.services.oa_publish import (
- count_fillable_fields,
- validate_condition_rules_against_form,
- validate_control_types,
- validate_draft_for_publish,
- validate_flow_tree,
- )
- def _linear_ok_flow():
- return {
- "id": "s1",
- "type": "start",
- "name": "发起人",
- "child": {
- "id": "a1",
- "type": "approval",
- "name": "部门负责人",
- "approverConfig": {"assigneeType": "dept_leader"},
- "child": {"id": "e1", "type": "end", "name": "结束"},
- },
- }
- def _condition_flow(rules=None, when=None, is_default=False):
- return {
- "type": "start",
- "name": "发起人",
- "child": {
- "type": "condition",
- "name": "条件",
- "conditionGroups": [
- {
- "name": "分支1",
- "rules": rules if rules is not None else [],
- "when": when,
- "isDefault": False,
- "child": {
- "type": "approval",
- "name": "审批A",
- "approverConfig": {"assigneeType": "dept_leader"},
- "child": {"type": "end", "name": "结束"},
- },
- },
- {
- "name": "默认",
- "rules": [],
- "isDefault": True,
- "child": {
- "type": "approval",
- "name": "审批B",
- "approverConfig": {"assigneeType": "dept_leader"},
- "child": {"type": "end", "name": "结束"},
- },
- },
- ],
- },
- }
- class TestOaPublishValidate(unittest.TestCase):
- def test_count_fillable_fields(self):
- schema = {
- "rule": [
- {"type": "html", "props": {"innerHTML": "说明"}},
- {"type": "input", "field": "title", "title": "标题"},
- {
- "type": "fcRow",
- "children": [
- {"type": "inputNumber", "field": "days", "title": "天数"},
- ],
- },
- ]
- }
- self.assertEqual(count_fillable_fields(schema), 2)
- self.assertEqual(count_fillable_fields({"rule": []}), 0)
- def test_flow_ok(self):
- self.assertEqual(validate_flow_tree(_linear_ok_flow()), [])
- def test_flow_missing_end(self):
- tree = {
- "type": "start",
- "name": "发起人",
- "child": {
- "type": "approval",
- "name": "审批",
- "approverConfig": {"assigneeType": "users", "userIds": ["u1"]},
- },
- }
- errs = validate_flow_tree(tree)
- self.assertTrue(any("结束" in e or "后续" in e for e in errs))
- def test_approval_users_empty_rejected(self):
- tree = {
- "type": "start",
- "child": {
- "type": "approval",
- "name": "审批",
- "approverConfig": {"assigneeType": "users", "userIds": []},
- "child": {"type": "end"},
- },
- }
- errs = validate_flow_tree(tree)
- self.assertTrue(any("指定成员" in e for e in errs))
- def test_cc_multi_types_ok(self):
- flow = {
- "type": "start",
- "child": {
- "type": "cc",
- "name": "抄送",
- "approverConfig": {
- "assigneeTypes": ["users", "dept_leader"],
- "userIds": ["u1"],
- },
- "child": {"type": "end"},
- },
- }
- self.assertEqual(validate_flow_tree(flow), [])
- def test_cc_multi_users_empty_fails(self):
- flow = {
- "type": "start",
- "child": {
- "type": "cc",
- "name": "抄送",
- "approverConfig": {
- "assigneeTypes": ["users", "initiator_select"],
- "userIds": [],
- },
- "child": {"type": "end"},
- },
- }
- errs = validate_flow_tree(flow)
- self.assertTrue(any("指定成员" in e for e in errs))
- def test_cc_no_types_fails(self):
- flow = {
- "type": "start",
- "child": {
- "type": "cc",
- "name": "抄送",
- "approverConfig": {},
- "child": {"type": "end"},
- },
- }
- errs = validate_flow_tree(flow)
- self.assertTrue(len(errs) >= 1)
- def test_approval_ignores_assignee_types_array(self):
- flow = {
- "type": "start",
- "child": {
- "type": "approval",
- "name": "审批",
- "approverConfig": {"assigneeTypes": ["dept_leader"]},
- "child": {"type": "end"},
- },
- }
- errs = validate_flow_tree(flow)
- self.assertTrue(len(errs) >= 1)
- def test_cc_legacy_single_assignee_type_ok(self):
- flow = {
- "type": "start",
- "child": {
- "type": "cc",
- "name": "抄送",
- "approverConfig": {"assigneeType": "initiator_select"},
- "child": {"type": "end"},
- },
- }
- self.assertEqual(validate_flow_tree(flow), [])
- def test_condition_branch_incomplete(self):
- tree = {
- "type": "start",
- "child": {
- "type": "condition",
- "name": "条件",
- "conditionGroups": [{"name": "大于3天", "rules": [], "child": None}],
- },
- }
- errs = validate_flow_tree(tree)
- self.assertTrue(any("下游" in e or "结束" in e for e in errs))
- def test_draft_requires_fillable_and_assignee(self):
- db = MagicMock()
- with patch(
- "app.services.oa_publish.validate_posts_referenced",
- return_value=[],
- ):
- errs = validate_draft_for_publish(
- db,
- tenant_no="zkxw",
- form_schema={"rule": [{"type": "html"}]},
- flow_tree={
- "type": "start",
- "child": {
- "type": "approval",
- "approverConfig": {"assigneeType": "users", "userIds": []},
- "child": {"type": "end"},
- },
- },
- )
- self.assertTrue(any("可填字段" in e for e in errs))
- self.assertTrue(any("指定成员" in e for e in errs))
- def test_valid_draft_passes_form_and_flow(self):
- db = MagicMock()
- with patch(
- "app.services.oa_publish.validate_posts_referenced",
- return_value=[],
- ):
- errs = validate_draft_for_publish(
- db,
- tenant_no="zkxw",
- form_schema={"rule": [{"type": "input", "field": "reason"}]},
- flow_tree=_linear_ok_flow(),
- )
- self.assertEqual(errs, [])
- def test_unknown_control_type_rejected(self):
- errs = validate_control_types(
- {"rule": [{"type": "aiMagicBox", "field": "x"}, {"type": "input", "field": "y"}]}
- )
- self.assertTrue(any("aiMagicBox" in e for e in errs))
- def test_upload_is_allowed(self):
- self.assertEqual(
- validate_control_types(
- {"rule": [{"type": "upload", "field": "files", "title": "上传"}]}
- ),
- [],
- )
- self.assertEqual(
- count_fillable_fields(
- {"rule": [{"type": "upload", "field": "files", "title": "上传"}]}
- ),
- 1,
- )
- class TestConditionPublish(unittest.TestCase):
- def test_when_only_rejected(self):
- errs = validate_flow_tree(_condition_flow(rules=[], when="{days} > 3"))
- self.assertTrue(any("可视化" in e or "重新配置" in e or "rules" in e for e in errs))
- def test_rules_empty_non_default_rejected(self):
- errs = validate_flow_tree(_condition_flow(rules=[]))
- self.assertTrue(any("条件" in e for e in errs))
- def test_rules_ok(self):
- errs = validate_flow_tree(
- _condition_flow(rules=[{"field": "days", "op": ">", "value": 3}])
- )
- self.assertEqual(errs, [])
- def test_invalid_field_reference(self):
- db = MagicMock()
- with patch(
- "app.services.oa_publish.validate_posts_referenced",
- return_value=[],
- ):
- errs = validate_draft_for_publish(
- db,
- tenant_no="zkxw",
- form_schema={
- "rule": [
- {
- "type": "inputNumber",
- "field": "days",
- "title": "天数",
- "useInCondition": True,
- "$required": True,
- }
- ]
- },
- flow_tree=_condition_flow(
- rules=[{"field": "other", "op": ">", "value": 1}]
- ),
- )
- self.assertTrue(any("无效字段" in e or "other" in e for e in errs))
- def test_field_not_flagged_rejected(self):
- db = MagicMock()
- with patch(
- "app.services.oa_publish.validate_posts_referenced",
- return_value=[],
- ):
- errs = validate_draft_for_publish(
- db,
- tenant_no="zkxw",
- form_schema={
- "rule": [
- {
- "type": "inputNumber",
- "field": "days",
- "title": "天数",
- "useInCondition": False,
- }
- ]
- },
- flow_tree=_condition_flow(
- rules=[{"field": "days", "op": ">", "value": 3}]
- ),
- )
- self.assertTrue(any("无效字段" in e or "参与" in e for e in errs))
- def test_initiator_condition_field_allowed(self):
- errs = validate_condition_rules_against_form(
- {"rule": []},
- _condition_flow(
- rules=[
- {
- "field": "__initiator__",
- "op": "contains",
- "value": ["u1"],
- }
- ]
- ),
- )
- self.assertEqual(errs, [])
- def test_initiator_condition_empty_value_rejected(self):
- errs = validate_condition_rules_against_form(
- {"rule": []},
- _condition_flow(
- rules=[
- {
- "field": "__initiator__",
- "op": "contains",
- "value": [],
- }
- ]
- ),
- )
- self.assertTrue(any("发起人" in e and ("包含" in e or "用户" in e) for e in errs))
- def test_initiator_condition_bad_op_rejected(self):
- errs = validate_condition_rules_against_form(
- {"rule": []},
- _condition_flow(
- rules=[
- {
- "field": "__initiator__",
- "op": "==",
- "value": ["u1"],
- }
- ]
- ),
- )
- self.assertTrue(any("发起人" in e and "包含" in e for e in errs))
- def test_dept_condition_field_allowed(self):
- errs = validate_condition_rules_against_form(
- {
- "rule": [
- {
- "type": "dept",
- "field": "Fdept",
- "title": "部门",
- "useInCondition": True,
- "$required": True,
- "props": {"multiple": True},
- }
- ]
- },
- _condition_flow(
- rules=[
- {"field": "Fdept", "op": "contains", "value": ["d1", "d2"]}
- ]
- ),
- )
- self.assertEqual(errs, [])
- def test_owner_dept_condition_field_allowed(self):
- errs = validate_condition_rules_against_form(
- {
- "rule": [
- {
- "type": "ownerDept",
- "field": "Fod",
- "title": "归属部门",
- "useInCondition": True,
- "$required": True,
- }
- ]
- },
- _condition_flow(
- rules=[{"field": "Fod", "op": "contains", "value": ["d1"]}]
- ),
- )
- self.assertEqual(errs, [])
- if __name__ == "__main__":
- unittest.main()
|