Files
AgentLab/backend/tests/test_blueprint_data_sources.py

119 lines
5.9 KiB
Python

"""「看哪些数」对到的数据源和表在不在(blueprints/store.data_source_problems)。
卡片的白名单只核形状、不查库:以前对到一个不存在的源(模型自己起的「本应用」),要到试运行发起时才报「绑定的工具
不存在」,用户还找不到在哪改。现在:
- 卡片上就照实说是哪一项、怎么改(bp.data_source_unknown / bp.data_table_unknown,带白话),挡住试运行和发布;
- 本应用的记录表写 @records,只有填报登记认,表名固定为 records;
- 模型自查(BlueprintChecker)拿得到这类问题,交回它改。
库和数字都是编的。
"""
from __future__ import annotations
import sqlite3
import uuid
import pytest
from httpx import ASGITransport, AsyncClient
from app.blueprints.ops import BpRefused, apply_bp_op
from app.blueprints.spec import empty_blueprint
from app.blueprints.store import SELF_RECORDS, data_source_problems
from app.data.introspect import introspect
from app.db.base import SessionLocal
from app.db.models import DataSource
from app.engine.runner import run_manager
from app.main import app
from tests import blueprint_samples as s
@pytest.fixture(autouse=True)
async def engine_up():
await run_manager.setup()
yield
await run_manager.shutdown()
@pytest.fixture
async def client():
async with AsyncClient(transport=ASGITransport(app=app), base_url="http://test") as c:
yield c
async def _source(tmp_path, *, explored: bool = True) -> str:
path = tmp_path / f"shop_{uuid.uuid4().hex[:6]}.db"
with sqlite3.connect(path) as db:
db.execute("CREATE TABLE orders (id INTEGER PRIMARY KEY, amount REAL, paid_at TEXT)")
name = f"shop_{uuid.uuid4().hex[:6]}"
async with SessionLocal() as session:
row = DataSource(name=name, kind="sqlite", database=str(path), readonly=True, enabled=True)
session.add(row)
await session.commit()
if explored:
row.schema_cache = await introspect(row)
await session.commit()
return name
def _spec(*tables: dict) -> dict:
spec = empty_blueprint()
spec["data"] = [{"id": f"m{i}", "label": s.item(f"指标{i}"), "table": s.item(t)} for i, t in enumerate(tables)]
return spec
def test_the_apps_own_records_table_has_a_fixed_name():
spec = empty_blueprint()
spec = apply_bp_op(spec, {"op": "bp_add", "path": "/data", "value": {"id": "total"}}, source="user")
spec = apply_bp_op(spec, {"op": "bp_set", "path": "/data/0/table",
"value": {"source": SELF_RECORDS, "table": "生产日报记录"}}, source="assistant")
assert spec["data"][0]["table"]["value"] == {"source": SELF_RECORDS, "table": "records"}
with pytest.raises(BpRefused):
apply_bp_op(spec, {"op": "bp_set", "path": "/data/0/table", "value": {"source": "", "table": "x"}}, source="user")
async def test_unknown_sources_and_tables_are_named_on_the_card(tmp_path):
known = await _source(tmp_path)
bare = await _source(tmp_path, explored=False)
spec = _spec({"source": "本应用", "table": "生产日报记录"}, {"source": known, "table": "orders"},
{"source": known, "table": "refunds"}, {"source": bare, "table": "anything"},
{"source": SELF_RECORDS, "table": "records"})
async with SessionLocal() as session:
board = await data_source_problems(session, spec, template_id="periodic_board")
records = await data_source_problems(session, spec, template_id="records")
by_path = {i["path"]: i for i in board}
# 不存在的源:说是哪一项、怎么改
assert by_path["/data/0/table"]["code"] == "bp.data_source_unknown" and "「本应用」不存在" in by_path["/data/0/table"]["message"]
# 对得上的不报;表结构探查过、表不在里面的照实说;没探查过的不猜
assert "/data/1/table" not in by_path and "/data/3/table" not in by_path
assert by_path["/data/2/table"]["code"] == "bp.data_table_unknown" and "refunds" in by_path["/data/2/table"]["message"]
# 本应用的记录表只有填报登记认
assert by_path["/data/4/table"]["code"] == "bp.data_source_unknown"
records_paths = {i["path"]: i for i in records}
assert "/data/4/table" not in records_paths
assert "本应用的记录表" in records_paths["/data/0/table"]["message"], "填报登记提示改选本应用的记录表"
assert all(i["level"] == "error" for i in board + records)
async def test_a_trial_on_an_unknown_source_is_refused_with_the_card_problem(client, tmp_path):
source = await s.register_source(s.make_source(tmp_path))
wf_id = await s.published_workflow(s.board_graph(source), f"周报-{uuid.uuid4().hex[:4]}")
bp = (await client.post("/api/blueprints", json={"source": {"kind": "workflow_version", "id": wf_id},
"scope": "shared"})).json()
await s.edit_spec(bp["id"], purpose=s.item("每周看销售额"), cadence=s.item(s.WEEKLY),
data=_spec({"source": "本应用", "table": "orders"})["data"])
got = (await client.get(f"/api/blueprints/{bp['id']}")).json()
issue = next(i for i in got["issues"] if i["code"] == "bp.data_source_unknown")
assert issue["path"] == "/data/0/table" and issue["plain"]["who"] == "you"
r = await client.post(f"/api/blueprints/{bp['id']}/trial", json={"inputs": s.PERIOD})
assert r.status_code == 409, r.text
assert "本应用" in r.text, "挡在发起之前,照实说是哪一项,不是「绑定的工具不存在」"
async def test_the_assistants_self_check_sees_unknown_sources():
from app.blueprints.generation import BlueprintApplier, BlueprintChecker
spec = _spec({"source": "本应用", "table": "生产日报记录"})
checker = BlueprintChecker(BlueprintApplier(spec), template_id="records")
issues = await checker.issues()
assert any(i["code"] == "bp.data_source_unknown" for i in issues)