mirror of
https://github.com/ThearchyHelios/AgentLab.git
synced 2026-11-15 18:41:59 +00:00
7739340607
- 问数据和个人应用的图放行绑定只读查询等合规工具的调用工具节点,只读和遮罩在这条路径上生效;被治理成写入的工具在只读运行里直接拒;知识库范围在工具层固定(图级默认值、参数名纠错、Agent 都改不了) - 个人应用的刷新和个人蓝图的试运行只有拥有者能读:运行、事件、证据、工件、文件、运行与审批列表、对比、可验证报告、质询、假设分析、提取工作流一并收紧;个人蓝图的试运行不论谁发起都按个人应用约束 - 共享蓝图只给拥有者、对应应用的管理者、管理员,看不到的一律 404;审核副本归审核人;管理员看别人的个人应用只有元数据、可以归档 - 记录表跨版本汇总(只读挂载旧版本、按当前列 UNION ALL、各版本各自固定高水位,缺的版本照实写进印章);停用前发起的运行可以继续写旧版本;更正沿用原提交人、更正人记审计 - 约定收口:待办种类 review、事件 file.extracted、表单错误 file、模板定义带示例与预置;前端类型一次补齐、去掉临时的局部类型;审核待办卡不再给打不开的链接;读附件事件有中文说法;检查脚本补断言
98 lines
4.8 KiB
Python
98 lines
4.8 KiB
Python
"""普通用户建应用的后端约定(docs/design/normal-user-apps.md 第五部分):前端类型(typesApp.ts、typesBlueprint.ts)对着的
|
||
那些常量和回包形状,在这里钉住,改了就要两边一起改。
|
||
|
||
- 待办种类有 review(个人应用提交审核),事件类型有 file.extracted,表单错误 code 有 file;
|
||
- 记录列表(RecordsPage)每个键都在:next_cursor 没有下一页为 null,state 是 ok / empty / preview;
|
||
- 审核待办的 payload 不论蓝图在不在,键都一样;
|
||
- 关节状态带模板声明的说法、类型、分组。
|
||
"""
|
||
from __future__ import annotations
|
||
|
||
import uuid
|
||
from types import SimpleNamespace
|
||
|
||
import pytest
|
||
|
||
import app.db.models # noqa: F401 - 建表前要先登记全部模型(conftest 按 Base.metadata 建)
|
||
import app.db.models_apps # noqa: F401
|
||
import app.db.models_blueprints # noqa: F401
|
||
import app.db.models_process # noqa: F401
|
||
from app.core.config import settings
|
||
from app.db.base import SessionLocal
|
||
|
||
RECORDS_PAGE_KEYS = {"table", "version", "columns", "items", "mine_only", "next_cursor", "state"}
|
||
REVIEW_PAYLOAD_KEYS = {"kind", "blueprint_id", "app_id", "name", "purpose", "template_id", "submitted_by", "note",
|
||
"copies"}
|
||
|
||
|
||
@pytest.fixture(autouse=True)
|
||
def data_dir(monkeypatch, tmp_path):
|
||
data = tmp_path / "data"
|
||
(data / "uploads").mkdir(parents=True)
|
||
monkeypatch.setattr(settings, "data_dir", data)
|
||
yield data
|
||
|
||
|
||
def test_constants_the_frontend_types_mirror():
|
||
from app.apps.spec import FORM_ERROR_CODES
|
||
from app.core.events import EventType
|
||
from app.db.models_process import TASK_KINDS
|
||
from app.files.store import FILE_ERROR_CODE
|
||
from app.process.tasks import REVIEW_KIND
|
||
|
||
assert REVIEW_KIND in TASK_KINDS
|
||
assert EventType.FILE_EXTRACTED == "file.extracted"
|
||
assert FILE_ERROR_CODE == "file" and FILE_ERROR_CODE in FORM_ERROR_CODES
|
||
|
||
|
||
async def test_records_page_always_has_every_key():
|
||
from app.api.deps import Principal
|
||
from app.apps.resolve import _records_list
|
||
from app.db.models_apps import App
|
||
from app.records.listing import page
|
||
from app.records.tables import ensure_table
|
||
|
||
async with SessionLocal() as session:
|
||
app = App(name=f"报损-{uuid.uuid4().hex[:4]}", created_by="建立人")
|
||
session.add(app)
|
||
await session.commit()
|
||
viewer = Principal(username="amy", role="admin", verified=True, mode="local")
|
||
async with SessionLocal() as session:
|
||
empty = await page(session, app, principal=viewer, role="manage")
|
||
assert set(empty) == RECORDS_PAGE_KEYS and empty["state"] == "empty" and empty["next_cursor"] is None
|
||
async with SessionLocal() as session:
|
||
await ensure_table(session, app, [{"name": "门店", "type": "string"}])
|
||
await session.commit()
|
||
async with SessionLocal() as session:
|
||
ok = await page(session, app, principal=viewer, role="manage")
|
||
assert set(ok) == RECORDS_PAGE_KEYS and ok["state"] == "ok" and ok["items"] == [] and ok["next_cursor"] is None
|
||
preview = await _records_list(None, SimpleNamespace(draft=True), {"who": "mine"})
|
||
assert set(preview) == RECORDS_PAGE_KEYS and preview["state"] == "preview", "草稿预览不说成「还没有记录表」"
|
||
|
||
|
||
async def test_review_payload_keeps_its_keys_when_the_blueprint_is_gone():
|
||
from app.blueprints.personal import review_payload
|
||
|
||
async with SessionLocal() as session:
|
||
got = await review_payload(session, SimpleNamespace(correlation_key="blueprint:gone", app_id="a1"))
|
||
assert set(got) == REVIEW_PAYLOAD_KEYS
|
||
assert got["kind"] == "review" and got["blueprint_id"] == "gone" and got["copies"] == [] and got["name"] is None
|
||
|
||
|
||
def test_joint_states_carry_the_templates_label_kind_and_group():
|
||
from app.blueprints.instantiate import instantiate
|
||
from app.blueprints.store import blueprint_out
|
||
from app.blueprints.templates import get_template
|
||
|
||
inst = instantiate(get_template("periodic_board").example())
|
||
defs = {j.id: j for j in inst.joint_defs}
|
||
assert defs and all(state["kind"] == defs[jid].kind and state["group"] == defs[jid].group
|
||
and state["label"] == (defs[jid].label or jid) for jid, state in inst.joints.items())
|
||
bp = SimpleNamespace(id="b1", name="周报", owner=None, scope="shared", status="drafting", spec={},
|
||
template_id="periodic_board", template_version=1, source={}, conversation_id=None,
|
||
workflow_id=None, app_id=None, last_trial_run_id=None, graph_hash=None, joints=inst.joints,
|
||
copied_from=None, created_at=None, updated_at=None)
|
||
out = blueprint_out(bp, issues=False)["joints"]
|
||
assert all(set(state) <= {"status", "by", "note", "label", "kind", "group"} for state in out.values())
|
||
assert all("value" not in state and "basis" not in state for state in out.values()), "填的值不给前端"
|