"""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. """ from datetime import timedelta import pytest from fastapi.testclient import TestClient from sqlmodel import Session, SQLModel, create_engine, select from agenteval.intelligent_eval.models import IntelligentEvalStatus from agenteval.storage.db import ( IntelligentEvalDB, IntelligentEvalDecisionLogDB, OpenClawCronPoolDB, utc_now, ) from agenteval.web.app import app from agenteval.web.deps import get_db @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.1 — heartbeat updates last_active_at / status / current_eval_id def test_heartbeat_updates_cron_fields(client: TestClient, db_session: Session): cron = OpenClawCronPoolDB( openclaw_cron_id="cron-1", status="idle", last_active_at=utc_now() - timedelta(hours=1), ) db_session.add(cron) db_session.commit() response = client.post( "/api/openclaw/crons/cron-1/heartbeat", json={"status": "busy", "current_eval_id": "eval-42"}, ) assert response.status_code == 200 db_session.refresh(cron) assert cron.status == "busy" assert cron.current_eval_id == "eval-42" def test_heartbeat_404_for_unknown_cron(client: TestClient): response = client.post( "/api/openclaw/crons/does-not-exist/heartbeat", json={"status": "busy", "current_eval_id": None}, ) assert response.status_code == 404 # 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"}