Files
AgentLab/backend/tests/test_contract_shapes.py
ThearchyHelios 7739340607 普通用户建应用集成收尾:
- 问数据和个人应用的图放行绑定只读查询等合规工具的调用工具节点,只读和遮罩在这条路径上生效;被治理成写入的工具在只读运行里直接拒;知识库范围在工具层固定(图级默认值、参数名纠错、Agent 都改不了)
- 个人应用的刷新和个人蓝图的试运行只有拥有者能读:运行、事件、证据、工件、文件、运行与审批列表、对比、可验证报告、质询、假设分析、提取工作流一并收紧;个人蓝图的试运行不论谁发起都按个人应用约束
- 共享蓝图只给拥有者、对应应用的管理者、管理员,看不到的一律 404;审核副本归审核人;管理员看别人的个人应用只有元数据、可以归档
- 记录表跨版本汇总(只读挂载旧版本、按当前列 UNION ALL、各版本各自固定高水位,缺的版本照实写进印章);停用前发起的运行可以继续写旧版本;更正沿用原提交人、更正人记审计
- 约定收口:待办种类 review、事件 file.extracted、表单错误 file、模板定义带示例与预置;前端类型一次补齐、去掉临时的局部类型;审核待办卡不再给打不开的链接;读附件事件有中文说法;检查脚本补断言
2026-10-15 13:55:19 +08:00

98 lines
4.8 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""普通用户建应用的后端约定(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()), "填的值不给前端"