"""Alert → auto-scale end-to-end link (Gitea issue #6 / T7). The desired end-to-end behaviour: a sustained high-utilization alert must provoke an auto-scale-up. Today the two paths are independent — `check_alerts` records alerts but never invokes `auto_scale`. This test guards the desired end-to-end loop; the xfail marks the missing link. """ from unittest.mock import AsyncMock, MagicMock import pytest from fastapi.testclient import TestClient from sqlmodel import Session, SQLModel, create_engine, select from agenteval.storage.db import IntelligentEvalTaskQueueDB, 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, ) from agenteval.intelligent_eval.alerts import AlertHistoryDB # noqa: F401 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]()) def _seed_high_utilization_pool(db_session: Session) -> None: """10 crons: 9 busy + 1 idle => pool_utilization = 0.9 (rule threshold).""" for i in range(10): db_session.add( OpenClawCronPoolDB( openclaw_cron_id=f"cron-{i}", status="busy" if i < 9 else "idle", last_active_at=utc_now(), ) ) db_session.commit() def test_check_alerts_records_task_backlog_alert( client: TestClient, db_session: Session ): """task_backlog threshold = 50 with duration_minutes = 0 must fire on call. Currently fails: AlertManager is per-request — triggered_at resets every request, so even duration_minutes=0 rules never re-fire on subsequent calls. Tracked in .scratch/v111-architecture-scan.md. """ from agenteval.storage.db import IntelligentEvalDB eval_db = IntelligentEvalDB( name="backlog-eval", target_id="t1", status="executing", started_at=utc_now(), ) db_session.add(eval_db) db_session.commit() for i in range(51): db_session.add( IntelligentEvalTaskQueueDB( eval_id=eval_db.id, status="pending", priority=1, reason="slot_due", ) ) db_session.commit() r = client.post("/api/openclaw/cron-pool/check-alerts") assert r.status_code == 200 types = {a["alert_type"] for a in r.json().get("alerts", [])} assert "task_backlog" in types def test_check_alerts_triggers_auto_scale_on_high_utilization( client: TestClient, db_session: Session ): """check_alerts must drive auto_scale so an alert fires a scale-up. Uses task_backlog (threshold=50, duration_minutes=0 → fires on first call) to avoid the multi-call wait required by pool_utilization (duration_minutes=10). """ from agenteval.intelligent_eval import cron_pool as cron_pool_mod from agenteval.storage.db import ( IntelligentEvalDB, IntelligentEvalTaskQueueDB, ) eval_db = IntelligentEvalDB( name="backlog-autoscale", target_id="t1", status="executing", started_at=utc_now(), ) db_session.add(eval_db) db_session.commit() for _ in range(51): db_session.add( IntelligentEvalTaskQueueDB( eval_id=eval_db.id, status="pending", priority=1, reason="slot_due", ) ) db_session.commit() real_scale_up = cron_pool_mod.scale_up cron_pool_mod.scale_up = AsyncMock(return_value=1) # type: ignore[assignment] try: client.post("/api/openclaw/cron-pool/check-alerts") assert cron_pool_mod.scale_up.await_count >= 1 finally: cron_pool_mod.scale_up = real_scale_up