常见故障自愈有上限,超限收敛终态且可见:任务 attempts 上限、会话过期、 planning 双闸、executing 超窗兜底、触发失败计数判死、孤儿 agent 双管、 fire-and-forget 触发;open_session 预算硬闸门、settle 按终态区分、报告 scores 归一化;cron 池遗留面全删。
129 lines
3.7 KiB
Python
129 lines
3.7 KiB
Python
"""Router ORM contract guards (Gitea issue #6 / T3).
|
|
|
|
These tests pin the observable behaviour of router endpoints that currently
|
|
embed ORM writes directly (S2). When those handlers are later moved into a
|
|
service, the tests should still pass unchanged — that is the contract.
|
|
"""
|
|
|
|
import pytest
|
|
from agenteval.intelligent_eval.models import IntelligentEvalStatus
|
|
from agenteval.storage.db import (
|
|
IntelligentEvalDB,
|
|
IntelligentEvalDecisionLogDB,
|
|
)
|
|
from agenteval.web.app import app
|
|
from agenteval.web.deps import get_db
|
|
from fastapi.testclient import TestClient
|
|
from sqlmodel import Session, SQLModel, create_engine, select
|
|
|
|
|
|
@pytest.fixture()
|
|
def client(tmp_path):
|
|
from agenteval.storage.db import ( # noqa: F401
|
|
IntelligentEvalDB,
|
|
IntelligentEvalSessionDB,
|
|
IntelligentEvalTaskQueueDB,
|
|
)
|
|
|
|
engine = create_engine(
|
|
f"sqlite:///{tmp_path / 'test.db'}",
|
|
connect_args={"check_same_thread": False},
|
|
)
|
|
SQLModel.metadata.create_all(engine)
|
|
session = Session(engine)
|
|
|
|
def override_get_db():
|
|
try:
|
|
yield session
|
|
finally:
|
|
pass
|
|
|
|
app.dependency_overrides[get_db] = override_get_db
|
|
yield TestClient(app)
|
|
app.dependency_overrides.clear()
|
|
session.close()
|
|
engine.dispose()
|
|
|
|
|
|
@pytest.fixture()
|
|
def db_session(client):
|
|
return next(app.dependency_overrides[get_db]())
|
|
|
|
|
|
# T3.2 — decision-logs POST persists to DB
|
|
|
|
|
|
def test_create_decision_log_persists(client: TestClient, db_session: Session):
|
|
import uuid
|
|
eval_db = IntelligentEvalDB(
|
|
id=str(uuid.uuid4()),
|
|
name="eval-dl",
|
|
target_id="t1",
|
|
status=IntelligentEvalStatus.EXECUTING.value,
|
|
)
|
|
db_session.add(eval_db)
|
|
db_session.commit()
|
|
|
|
response = client.post(
|
|
f"/api/intelligent-evals/{eval_db.id}/decision-logs",
|
|
json={
|
|
"decision_type": "execute_session",
|
|
"reason": "slot_due",
|
|
"context": {"slot": "8-10h", "deficit": 2},
|
|
"cron_id": "cron-1",
|
|
},
|
|
)
|
|
assert response.status_code == 200
|
|
|
|
logs = db_session.exec(
|
|
select(IntelligentEvalDecisionLogDB).where(
|
|
IntelligentEvalDecisionLogDB.eval_id == eval_db.id
|
|
)
|
|
).all()
|
|
assert len(logs) == 1
|
|
assert logs[0].decision_type == "execute_session"
|
|
assert logs[0].reason == "slot_due"
|
|
|
|
|
|
def test_create_decision_log_404_for_unknown_eval(client: TestClient):
|
|
import uuid
|
|
response = client.post(
|
|
f"/api/intelligent-evals/{uuid.uuid4()}/decision-logs",
|
|
json={
|
|
"decision_type": "execute_session",
|
|
"reason": "slot_due",
|
|
"context": {},
|
|
"cron_id": "cron-1",
|
|
},
|
|
)
|
|
assert response.status_code == 404
|
|
|
|
|
|
# T3.3 — decision-logs GET lists logs
|
|
|
|
|
|
def test_list_decision_logs_returns_inserted(client: TestClient, db_session: Session):
|
|
import uuid
|
|
eval_db = IntelligentEvalDB(
|
|
id=str(uuid.uuid4()),
|
|
name="eval-dl-list",
|
|
target_id="t1",
|
|
status=IntelligentEvalStatus.EXECUTING.value,
|
|
)
|
|
db_session.add(eval_db)
|
|
db_session.commit()
|
|
|
|
for dtype in ("execute_session", "wait", "start_analysis"):
|
|
r = client.post(
|
|
f"/api/intelligent-evals/{eval_db.id}/decision-logs",
|
|
json={"decision_type": dtype, "reason": "test", "context": {}, "cron_id": "cron-1"},
|
|
)
|
|
assert r.status_code == 200
|
|
|
|
response = client.get(f"/api/intelligent-evals/{eval_db.id}/decision-logs")
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
assert "logs" in data
|
|
types = {log["decision_type"] for log in data["logs"]}
|
|
assert types == {"execute_session", "wait", "start_analysis"}
|