aps-agent/tests/golden/test_e2e_acceptance.py

99 lines
4.0 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.

# ============================================================
# M-G 端到端验收:数据入库 → 工时维护 → 柔性排产 → 外部 skill 试排 → 知识问答
# (康尼现场 Excel 在仓库外,链路用演示厂数据走通同一代码路径)
# ============================================================
from __future__ import annotations
import asyncio
import pytest
from server.contracts import AgentReply, IntentResult
from server.state.seed import ensure_flex_seed, seed_world
class FakeStore:
def __init__(self):
self.data = seed_world()
ensure_flex_seed(self.data)
self._counters: dict[str, int] = {}
def next_id(self, kind: str) -> int:
self._counters[kind] = self._counters.get(kind, 0) + 1
return self._counters[kind]
def save(self) -> None:
pass
@pytest.fixture(autouse=True)
def _isolate(tmp_path, monkeypatch):
monkeypatch.setenv("APS_DB_PATH", str(tmp_path / "master.db"))
monkeypatch.setenv("APS_SKILLS_PATH", str(tmp_path / "skills.json"))
monkeypatch.delenv("APS_DB_DISABLED", raising=False)
import server.agent_core.skills as sk
from server.db.database import reset_engine
sk._registry = None
reset_engine()
yield
sk._registry = None
reset_engine()
def test_end_to_end_master_data_to_scheduling_to_knowledge():
store = FakeStore()
sid = "e2e"
# ---- 1. 数据地基:主数据入库 → 投影回 world 等价 ----
from server.db.database import init_db
from server.db.sync import db_has_master, db_to_world, world_to_db
init_db()
world_to_db(store.data, project_code="e2e-demo")
assert db_has_master("e2e-demo")
projected: dict = {}
db_to_world(projected, project_code="e2e-demo")
assert len(projected["flexOrders"]) == len(store.data["flexOrders"])
assert len(projected["flexRoutings"]) == len(store.data["flexRoutings"])
# ---- 2. 工时维护:制造一个「待维护」缺口,走 P2 确认卡补齐 ----
for e in store.data["flexEquipment"]:
e.get("opStdTime", {}).pop("OP-WELD", None)
from server.aps_domain.readiness import check_readiness
before = check_readiness(store.data)
assert before["summary"]["blocked"] > 0, "摘掉工时后应判定不可排"
from server.aps_domain.workflow import execute_confirmed, handle_intent
staged = asyncio.run(handle_intent(
store, sid,
IntentResult(intent="flex.time.update",
params={"operationCode": "OP-WELD", "stdMin": 25},
confidence=1.0, source="LLM"),
actor="e2e"))
assert isinstance(staged, AgentReply)
confirm = [b for b in staged.blocks if b.type == "confirm-card"]
assert confirm, "补工时应出 P2 确认卡"
msg = execute_confirmed(store, confirm[0].props["confirmId"], approve=True, actor="e2e")
assert "工时已更新" in msg
assert check_readiness(store.data)["summary"]["blocked"] == 0, "补齐工时后应全绿"
# ---- 3. 排产:全绿 → flex.schedule 工具请求 → 产出工单 ----
schedule_reply = asyncio.run(handle_intent(
store, sid,
IntentResult(intent="flex.schedule", params={}, confidence=1.0, source="LLM"),
actor="e2e"))
assert isinstance(schedule_reply, AgentReply)
assert store.data.get("flexWorkOrders"), "试排应产出柔性工单"
# ---- 4. 外部 skill 试排:algo.stub 经中性 DTO 写回草稿 ----
from server.engines.external_engine import run_external_flex
summary = run_external_flex(store, skill_id="algo.stub")
assert summary["woCount"] >= 1 and summary["runId"]
# ---- 5. 知识问答:检索命中必带出处+版本 ----
from server.knowledge.assets import get_knowledge
from server.knowledge.retrieval import hybrid_search
units = get_knowledge().iter_search_units()
hits = hybrid_search(units, "车削类零件的工艺路线怎么生成", top_k=3)
assert hits, "机加工工艺知识应命中"
assert all(h.get("assetId") and h.get("version") for h in hits), "命中必带出处与版本"