404 lines
13 KiB
Python
404 lines
13 KiB
Python
"""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()
|