- 发布改为可选「确认审核 / 直接发布」:链条编辑页与圆桌「业务链条」弹窗行为一致;审核中可撤回 - 管理员在业务链条列表新增「待审核」筛选,审核发布后进入公共库 - 后端 roundtable_chains 新增 pending_review 字段(启动期自动补列)与 scope=pending 筛选;新增审核流程测试 - 编辑器席位去掉「岗位职责」输入框(保存时存量值原样保留)
103 lines
4.2 KiB
Python
103 lines
4.2 KiB
Python
"""业务链条「发布审核」流程测试。
|
||
|
||
覆盖:
|
||
- store:``pending_review`` 随 create(缺省 False)/ update 持久化;
|
||
- store:``scope="pending"`` 仅管理员返回全部待审核链条;非管理员按 ``mine`` 退化;
|
||
- 完整审核流:owner 提交审核 → 管理员在 pending 列表看到 → 发布(``is_public=True`` +
|
||
``pending_review=False``)后从 pending 列表消失、进入 public 列表。
|
||
"""
|
||
from __future__ import annotations
|
||
|
||
import pytest
|
||
from sqlalchemy.ext.asyncio import async_sessionmaker, create_async_engine
|
||
|
||
|
||
async def _make_repo():
|
||
from deerflow.persistence.base import Base
|
||
from deerflow.persistence.roundtable_chains.model import RoundtableChainRow # noqa: F401 注册表
|
||
from deerflow.persistence.roundtable_chains.sql import RoundtableChainRepository
|
||
|
||
engine = create_async_engine("sqlite+aiosqlite:///:memory:")
|
||
async with engine.begin() as conn:
|
||
await conn.run_sync(Base.metadata.create_all)
|
||
sf = async_sessionmaker(engine, expire_on_commit=False)
|
||
return RoundtableChainRepository(sf), engine
|
||
|
||
|
||
_SEATS = [
|
||
{"agent_id": "a", "name": "情报收集", "description": ""},
|
||
{"agent_id": "b", "name": "环境评估", "description": ""},
|
||
]
|
||
|
||
|
||
@pytest.mark.asyncio
|
||
async def test_pending_review_flag_roundtrip():
|
||
repo, engine = await _make_repo()
|
||
try:
|
||
row = await repo.create_chain("u1", {"id": "c1", "title": "链", "seats": _SEATS})
|
||
# 缺省 False。
|
||
assert row["pending_review"] is False
|
||
|
||
# owner 提交审核 → True。
|
||
updated = await repo.update_chain("c1", "u1", pending_review=True)
|
||
assert updated is not None and updated["pending_review"] is True
|
||
|
||
# 撤回 / 管理员发布清回 → False。
|
||
updated = await repo.update_chain("c1", "u1", pending_review=False)
|
||
assert updated is not None and updated["pending_review"] is False
|
||
finally:
|
||
await engine.dispose()
|
||
|
||
|
||
@pytest.mark.asyncio
|
||
async def test_scope_pending_lists_all_users_pending_for_admin():
|
||
repo, engine = await _make_repo()
|
||
try:
|
||
await repo.create_chain("u1", {"id": "c1", "title": "U1 待审", "seats": _SEATS})
|
||
await repo.create_chain("u1", {"id": "c2", "title": "U1 草稿", "seats": _SEATS})
|
||
await repo.create_chain("u2", {"id": "c3", "title": "U2 待审", "seats": _SEATS})
|
||
await repo.update_chain("c1", "u1", pending_review=True)
|
||
await repo.update_chain("c3", "u2", pending_review=True)
|
||
|
||
# 管理员 pending → 只含两条待审核链条(跨账号)。
|
||
rows = await repo.list_chains("admin", scope="pending", is_admin=True)
|
||
assert {r["id"] for r in rows} == {"c1", "c3"}
|
||
|
||
# 非管理员误传 pending(is_admin=False)→ 退化为 mine:只见自己的(c1/c2),
|
||
# 绝不越权看到他人待审核链条。
|
||
rows = await repo.list_chains("u1", scope="pending", is_admin=False)
|
||
assert {r["id"] for r in rows} == {"c1", "c2"}
|
||
finally:
|
||
await engine.dispose()
|
||
|
||
|
||
@pytest.mark.asyncio
|
||
async def test_full_review_flow_owner_submit_admin_publish():
|
||
repo, engine = await _make_repo()
|
||
try:
|
||
await repo.create_chain("u1", {"id": "c1", "title": "走审核的链", "seats": _SEATS})
|
||
|
||
# ① owner 提交审核。
|
||
await repo.update_chain("c1", "u1", pending_review=True)
|
||
|
||
# ② 管理员在「待审核」列表看到。
|
||
pending = await repo.list_chains("admin", scope="pending", is_admin=True)
|
||
assert [r["id"] for r in pending] == ["c1"]
|
||
|
||
# ③ 管理员审核发布:is_public=True + 清除待审核标记。
|
||
published = await repo.update_chain(
|
||
"c1", "admin", is_public=True, pending_review=False, is_admin=True
|
||
)
|
||
assert published is not None
|
||
assert published["is_public"] is True
|
||
assert published["pending_review"] is False
|
||
# owner 不变。
|
||
assert published["owner_id"] == "u1"
|
||
|
||
# ④ 发布后:pending 列表为空,public 列表可见(任何账号)。
|
||
assert await repo.list_chains("admin", scope="pending", is_admin=True) == []
|
||
public = await repo.list_chains("u2", scope="public")
|
||
assert [r["id"] for r in public] == ["c1"]
|
||
finally:
|
||
await engine.dispose()
|