| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527 |
- """OA 实例评论:附件枚举与引用校验"""
- import json
- import unittest
- from datetime import datetime
- from app.services.oa_instance_comment import (
- enumerate_form_attachments,
- normalize_attachment_refs,
- )
- class TestEnumerateFormAttachments(unittest.TestCase):
- def test_groups_upload_fields(self):
- schema = {
- "rule": [
- {
- "type": "upload",
- "field": "files",
- "title": "附件",
- },
- {"type": "input", "field": "name", "title": "名称"},
- ]
- }
- form = {
- "files": [
- {"url": "https://x/a.pdf", "name": "a.pdf"},
- {"url": "https://x/b.pdf", "name": "b.pdf"},
- ],
- "name": "demo",
- }
- items = enumerate_form_attachments(schema, form)
- self.assertEqual(len(items), 1)
- self.assertEqual(items[0]["fieldKey"], "files")
- self.assertEqual(items[0]["fieldLabel"], "附件")
- self.assertEqual(len(items[0]["files"]), 2)
- self.assertEqual(items[0]["files"][0]["fileName"], "a.pdf")
- def test_skips_empty_upload(self):
- schema = {"rule": [{"type": "fcUpload", "field": "f", "title": "F"}]}
- self.assertEqual(enumerate_form_attachments(schema, {"f": []}), [])
- class TestNormalizeAttachmentRefs(unittest.TestCase):
- def test_accepts_valid_ref(self):
- schema = {"rule": [{"type": "upload", "field": "files", "title": "附件"}]}
- form = {"files": [{"url": "https://x/a.pdf", "name": "a.pdf"}]}
- out = normalize_attachment_refs(
- schema,
- form,
- [{"fieldKey": "files", "url": "https://x/a.pdf", "fileName": "wrong"}],
- )
- self.assertEqual(out[0]["fileName"], "a.pdf")
- self.assertEqual(out[0]["fieldLabel"], "附件")
- def test_rejects_foreign_url(self):
- from app.services.oa_engine import OaEngineError
- schema = {"rule": [{"type": "upload", "field": "files", "title": "附件"}]}
- form = {"files": [{"url": "https://x/a.pdf", "name": "a.pdf"}]}
- with self.assertRaises(OaEngineError):
- normalize_attachment_refs(
- schema,
- form,
- [{"fieldKey": "files", "url": "https://evil/x.pdf"}],
- )
- def test_marks_form_ref_source(self):
- schema = {"rule": [{"type": "upload", "field": "files", "title": "附件"}]}
- form = {"files": [{"url": "https://x/a.pdf", "name": "a.pdf"}]}
- out = normalize_attachment_refs(
- schema,
- form,
- [{"url": "https://x/a.pdf"}],
- )
- self.assertEqual(out[0]["source"], "form")
- def test_accepts_tenant_upload_and_uses_stored_name(self):
- from sqlalchemy import create_engine
- from sqlalchemy.orm import sessionmaker
- from app.models.models import BizAttachment
- engine = create_engine("sqlite:///:memory:")
- BizAttachment.__table__.create(engine)
- db = sessionmaker(bind=engine)()
- db.add(
- BizAttachment(
- id="att1",
- original_name="note.pdf",
- file_url="oa-approval/note.pdf",
- tenant_no="t1",
- create_dept_id="",
- )
- )
- db.commit()
- out = normalize_attachment_refs(
- None,
- {},
- [{"source": "upload", "url": "oa-approval/note.pdf", "fileName": "hack.pdf"}],
- db=db,
- tenant_no="t1",
- )
- self.assertEqual(out[0]["source"], "upload")
- self.assertEqual(out[0]["fileName"], "note.pdf")
- self.assertEqual(out[0]["fieldKey"], "")
- db.close()
- def test_rejects_upload_from_other_tenant_or_unknown_url(self):
- from sqlalchemy import create_engine
- from sqlalchemy.orm import sessionmaker
- from app.models.models import BizAttachment
- from app.services.oa_engine import OaEngineError
- engine = create_engine("sqlite:///:memory:")
- BizAttachment.__table__.create(engine)
- db = sessionmaker(bind=engine)()
- db.add(
- BizAttachment(
- id="att2",
- original_name="note.pdf",
- file_url="oa-approval/note.pdf",
- tenant_no="other",
- create_dept_id="",
- )
- )
- db.commit()
- with self.assertRaises(OaEngineError):
- normalize_attachment_refs(
- None,
- {},
- [{"source": "upload", "url": "oa-approval/note.pdf"}],
- db=db,
- tenant_no="t1",
- )
- with self.assertRaises(OaEngineError):
- normalize_attachment_refs(
- None,
- {},
- [{"source": "upload", "url": "https://evil.example/a.pdf"}],
- db=db,
- tenant_no="t1",
- )
- db.close()
- class TestCommentCrud(unittest.TestCase):
- def setUp(self):
- from sqlalchemy import create_engine
- from sqlalchemy.orm import sessionmaker
- from app.models.models import OaAppVersion, OaInstance, OaInstanceComment, SysUser
- self.engine = create_engine("sqlite:///:memory:")
- OaAppVersion.__table__.create(self.engine)
- OaInstance.__table__.create(self.engine)
- OaInstanceComment.__table__.create(self.engine)
- SysUser.__table__.create(self.engine)
- Session = sessionmaker(bind=self.engine, autoflush=False, autocommit=False)
- self.db = Session()
- now = datetime.now()
- schema = {
- "rule": [
- {"type": "upload", "field": "files", "title": "附件"},
- ]
- }
- self.db.add(
- OaAppVersion(
- id="v1",
- app_id="app1",
- version_no=1,
- status="published",
- is_latest_published="1",
- form_schema_json=json.dumps(schema, ensure_ascii=False),
- flow_tree_json="{}",
- advanced_settings_json="{}",
- tenant_no="zkxw",
- create_dept_id="",
- create_time=now,
- update_time=now,
- del_flag=0,
- )
- )
- self.db.add(
- OaInstance(
- id="i1",
- app_id="app1",
- app_version_id="v1",
- title="t",
- status="running",
- initiator_user_id="u0",
- initiator_dept_id="d1",
- form_data_json=json.dumps(
- {"files": [{"url": "https://x/a.pdf", "name": "a.pdf"}]},
- ensure_ascii=False,
- ),
- tenant_no="zkxw",
- create_dept_id="d1",
- create_time=now,
- update_time=now,
- del_flag=0,
- current_round=1,
- )
- )
- self.db.add(
- SysUser(
- id="u1",
- tenant_no="zkxw",
- user_no="u1",
- nick_name="张三",
- email="",
- phone_no="",
- password="",
- status="1",
- create_dept_id="",
- del_flag=0,
- create_time=now,
- update_time=now,
- )
- )
- self.db.commit()
- def test_create_list_revoke(self):
- from app.services import oa_instance_comment as svc
- row = svc.create_comment(
- self.db,
- instance_id="i1",
- tenant_no="zkxw",
- user_id="u1",
- dept_id="d1",
- content="请看附件",
- attachment_refs=[{"fieldKey": "files", "url": "https://x/a.pdf"}],
- )
- self.assertEqual(row["status"], "normal")
- self.assertTrue(row["canRevoke"])
- self.assertEqual(len(row["attachmentRefs"]), 1)
- listed = svc.list_comments(
- self.db, instance_id="i1", tenant_no="zkxw", viewer_user_id="u1"
- )
- self.assertEqual(len(listed), 1)
- revoked = svc.revoke_comment(
- self.db,
- instance_id="i1",
- comment_id=row["id"],
- tenant_no="zkxw",
- user_id="u1",
- )
- self.assertEqual(revoked["status"], "revoked")
- self.assertFalse(revoked["canRevoke"])
- again = svc.revoke_comment(
- self.db,
- instance_id="i1",
- comment_id=row["id"],
- tenant_no="zkxw",
- user_id="u1",
- )
- self.assertEqual(again["status"], "revoked")
- def test_cannot_comment_when_not_running(self):
- from app.services import oa_instance_comment as svc
- from app.services.oa_engine import OaEngineError
- from app.models.models import OaInstance
- inst = self.db.query(OaInstance).filter(OaInstance.id == "i1").one()
- inst.status = "completed"
- self.db.commit()
- with self.assertRaises(OaEngineError):
- svc.create_comment(
- self.db,
- instance_id="i1",
- tenant_no="zkxw",
- user_id="u1",
- dept_id="d1",
- content="x",
- attachment_refs=None,
- )
- class TestCommentMentionGrant(unittest.TestCase):
- def setUp(self):
- from sqlalchemy import create_engine
- from sqlalchemy.orm import sessionmaker
- from app.models.models import (
- OaAppVersion,
- OaInstance,
- OaInstanceComment,
- OaInstanceViewGrant,
- OaTask,
- SysUser,
- )
- self.engine = create_engine("sqlite:///:memory:")
- for table in (
- OaAppVersion.__table__,
- OaInstance.__table__,
- OaInstanceComment.__table__,
- OaInstanceViewGrant.__table__,
- OaTask.__table__,
- SysUser.__table__,
- ):
- table.create(self.engine)
- Session = sessionmaker(bind=self.engine, autoflush=False, autocommit=False)
- self.db = Session()
- now = datetime.now()
- self.db.add(
- OaAppVersion(
- id="v1",
- app_id="app1",
- version_no=1,
- status="published",
- is_latest_published="1",
- form_schema_json="{}",
- flow_tree_json="{}",
- advanced_settings_json="{}",
- tenant_no="zkxw",
- create_dept_id="",
- create_time=now,
- update_time=now,
- del_flag=0,
- )
- )
- self.db.add(
- OaInstance(
- id="i1",
- app_id="app1",
- app_version_id="v1",
- title="t",
- status="running",
- initiator_user_id="u0",
- initiator_dept_id="d1",
- form_data_json="{}",
- tenant_no="zkxw",
- create_dept_id="d1",
- create_time=now,
- update_time=now,
- del_flag=0,
- current_round=1,
- )
- )
- for uid, name, status in (("u1", "张三", "1"), ("u2", "李四", "1"), ("u3", "王五", "0")):
- self.db.add(
- SysUser(
- id=uid,
- tenant_no="zkxw",
- user_no=uid,
- nick_name=name,
- email="",
- phone_no="",
- password="",
- status=status,
- create_dept_id="",
- del_flag=0,
- create_time=now,
- update_time=now,
- )
- )
- self.db.commit()
- def test_mention_grants_view_and_is_idempotent(self):
- from app.models.models import OaInstanceViewGrant
- from app.services import oa_instance_comment as svc
- row = svc.create_comment(
- self.db,
- instance_id="i1",
- tenant_no="zkxw",
- user_id="u1",
- dept_id="d1",
- content="",
- attachment_refs=None,
- mention_user_ids=["u2", "u2", "u1"],
- )
- self.assertEqual(row["content"], "")
- self.assertEqual([m["userId"] for m in row["mentions"]], ["u2"])
- self.assertEqual(row["mentions"][0]["userName"], "李四")
- grants = (
- self.db.query(OaInstanceViewGrant)
- .filter(OaInstanceViewGrant.del_flag == 0)
- .all()
- )
- self.assertEqual(len(grants), 1)
- self.assertEqual(grants[0].user_id, "u2")
- self.assertEqual(grants[0].source, "comment_mention")
- svc.create_comment(
- self.db,
- instance_id="i1",
- tenant_no="zkxw",
- user_id="u1",
- dept_id="d1",
- content="再提一次",
- attachment_refs=None,
- mention_user_ids=["u2"],
- )
- grants = (
- self.db.query(OaInstanceViewGrant)
- .filter(OaInstanceViewGrant.del_flag == 0)
- .all()
- )
- self.assertEqual(len(grants), 1)
- def test_rejects_empty_without_mention(self):
- from app.services import oa_instance_comment as svc
- from app.services.oa_engine import OaEngineError
- with self.assertRaises(OaEngineError):
- svc.create_comment(
- self.db,
- instance_id="i1",
- tenant_no="zkxw",
- user_id="u1",
- dept_id="d1",
- content=" ",
- attachment_refs=None,
- mention_user_ids=None,
- )
- def test_allows_upload_only_comment(self):
- from app.models.models import BizAttachment
- from app.services import oa_instance_comment as svc
- BizAttachment.__table__.create(self.engine)
- self.db.add(
- BizAttachment(
- id="att-c1",
- original_name="note.pdf",
- file_url="oa-approval/note.pdf",
- tenant_no="zkxw",
- create_dept_id="",
- )
- )
- self.db.commit()
- row = svc.create_comment(
- self.db,
- instance_id="i1",
- tenant_no="zkxw",
- user_id="u1",
- dept_id="d1",
- content=" ",
- attachment_refs=[{"source": "upload", "url": "oa-approval/note.pdf"}],
- mention_user_ids=None,
- )
- self.assertEqual(row["content"], "")
- self.assertEqual(row["attachmentRefs"][0]["source"], "upload")
- self.assertEqual(row["attachmentRefs"][0]["fileName"], "note.pdf")
- def test_rejects_disabled_user(self):
- from app.services import oa_instance_comment as svc
- from app.services.oa_engine import OaEngineError
- with self.assertRaises(OaEngineError):
- svc.create_comment(
- self.db,
- instance_id="i1",
- tenant_no="zkxw",
- user_id="u1",
- dept_id="d1",
- content="x",
- attachment_refs=None,
- mention_user_ids=["u3"],
- )
- def test_revoke_keeps_grant_and_list_skips_participants(self):
- from app.models.models import OaInstance, OaInstanceViewGrant, OaTask
- from app.services import oa_instance_comment as svc
- row = svc.create_comment(
- self.db,
- instance_id="i1",
- tenant_no="zkxw",
- user_id="u1",
- dept_id="d1",
- content="请看",
- attachment_refs=None,
- mention_user_ids=["u2"],
- )
- svc.revoke_comment(
- self.db,
- instance_id="i1",
- comment_id=row["id"],
- tenant_no="zkxw",
- user_id="u1",
- )
- self.assertEqual(
- self.db.query(OaInstanceViewGrant)
- .filter(OaInstanceViewGrant.del_flag == 0)
- .count(),
- 1,
- )
- self.assertEqual(
- svc.list_mentioned_instance_ids(self.db, tenant_no="zkxw", user_id="u2"),
- ["i1"],
- )
- now = datetime.now()
- self.db.add(
- OaTask(
- id="t-cc",
- instance_id="i1",
- task_type="cc",
- assignee_user_id="u2",
- status="done",
- tenant_no="zkxw",
- create_dept_id="",
- create_time=now,
- update_time=now,
- del_flag=0,
- round_no=1,
- is_read=0,
- )
- )
- self.db.commit()
- # 即使已是抄送人,被@仍应出现在「提到我的」
- self.assertEqual(
- svc.list_mentioned_instance_ids(self.db, tenant_no="zkxw", user_id="u2"),
- ["i1"],
- )
- inst = self.db.query(OaInstance).filter(OaInstance.id == "i1").one()
- inst.initiator_user_id = "u2"
- self.db.commit()
- # 即使已是发起人,被@仍应出现在「提到我的」
- self.assertEqual(
- svc.list_mentioned_instance_ids(self.db, tenant_no="zkxw", user_id="u2"),
- ["i1"],
- )
|