- Add metrics.py with pool utilization, task backlog, stuck rate, avg processing time, eval completion rate - Add alerts.py with alert rules (pool utilization > 90%, task backlog > 50, stuck rate > 10%) - Implement alert history and webhook notifications - Add metrics and alerts APIs - Add database migration for alert history table - Add 11 unit tests for metrics, 10 unit tests for alerts, 8 integration tests - Update migration tests to include new alert history table All 853 tests passing.
279 lines
7.9 KiB
Python
279 lines
7.9 KiB
Python
"""Integration tests for metrics and alerts API."""
|
|
|
|
from datetime import timedelta
|
|
|
|
import pytest
|
|
from fastapi.testclient import TestClient
|
|
from sqlmodel import Session, SQLModel, create_engine, select
|
|
from unittest.mock import patch, MagicMock
|
|
|
|
from agenteval.intelligent_eval.models import IntelligentEvalStatus
|
|
from agenteval.storage.db import (
|
|
IntelligentEvalDB,
|
|
IntelligentEvalTaskQueueDB,
|
|
OpenClawCronPoolDB,
|
|
utc_now,
|
|
)
|
|
from agenteval.web.app import app
|
|
from agenteval.web.deps import get_db
|
|
|
|
|
|
@pytest.fixture()
|
|
def client(tmp_path):
|
|
"""Create a TestClient with a fresh database."""
|
|
from agenteval.storage.db import ( # noqa: F401
|
|
IntelligentEvalDB,
|
|
IntelligentEvalTaskQueueDB,
|
|
OpenClawCronPoolDB,
|
|
)
|
|
from agenteval.intelligent_eval.alerts import AlertHistoryDB
|
|
|
|
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
|
|
client = TestClient(app)
|
|
yield client
|
|
app.dependency_overrides.clear()
|
|
session.close()
|
|
engine.dispose()
|
|
|
|
|
|
@pytest.fixture()
|
|
def db_session(client):
|
|
"""Get the database session from the client fixture."""
|
|
return next(app.dependency_overrides[get_db]())
|
|
|
|
|
|
def test_get_metrics_api(client: TestClient, db_session: Session):
|
|
"""Test getting metrics via API."""
|
|
# Create some test data
|
|
for i in range(5):
|
|
cron = OpenClawCronPoolDB(
|
|
openclaw_cron_id=f"cron-{i}",
|
|
status="busy" if i < 3 else "idle",
|
|
last_active_at=utc_now(),
|
|
)
|
|
db_session.add(cron)
|
|
|
|
db_session.commit()
|
|
|
|
response = client.get("/api/openclaw/cron-pool/metrics")
|
|
assert response.status_code == 200
|
|
|
|
data = response.json()
|
|
assert "metrics" in data
|
|
assert "pool_utilization" in data["metrics"]
|
|
assert "task_backlog" in data["metrics"]
|
|
assert "stuck_rate" in data["metrics"]
|
|
assert "avg_processing_time_seconds" in data["metrics"]
|
|
assert "eval_completion_rate" in data["metrics"]
|
|
|
|
assert data["metrics"]["pool_utilization"] == 0.6
|
|
|
|
|
|
def test_check_alerts_api_no_alerts(client: TestClient, db_session: Session):
|
|
"""Test checking alerts when no rules are triggered."""
|
|
# Create healthy state
|
|
for i in range(5):
|
|
cron = OpenClawCronPoolDB(
|
|
openclaw_cron_id=f"cron-{i}",
|
|
status="idle",
|
|
last_active_at=utc_now(),
|
|
)
|
|
db_session.add(cron)
|
|
|
|
db_session.commit()
|
|
|
|
response = client.post("/api/openclaw/cron-pool/check-alerts")
|
|
assert response.status_code == 200
|
|
|
|
data = response.json()
|
|
assert data["success"] is True
|
|
assert data["alerts_triggered"] == 0
|
|
|
|
|
|
def test_check_alerts_api_with_alerts(client: TestClient, db_session: Session):
|
|
"""Test checking alerts when rules are triggered."""
|
|
# Create high backlog
|
|
for i in range(60):
|
|
task = IntelligentEvalTaskQueueDB(
|
|
eval_id=f"eval-{i}",
|
|
status="pending",
|
|
priority=1,
|
|
reason="slot_due",
|
|
)
|
|
db_session.add(task)
|
|
|
|
db_session.commit()
|
|
|
|
response = client.post("/api/openclaw/cron-pool/check-alerts")
|
|
assert response.status_code == 200
|
|
|
|
data = response.json()
|
|
assert data["success"] is True
|
|
assert data["alerts_triggered"] > 0
|
|
|
|
# Should have task_backlog alert
|
|
backlog_alerts = [a for a in data["alerts"] if a["alert_type"] == "task_backlog"]
|
|
assert len(backlog_alerts) == 1
|
|
assert backlog_alerts[0]["metric_value"] == 60
|
|
|
|
|
|
def test_get_alert_history_api(client: TestClient, db_session: Session):
|
|
"""Test getting alert history via API."""
|
|
# Create some alerts
|
|
from agenteval.intelligent_eval.alerts import AlertHistoryDB
|
|
|
|
for i in range(3):
|
|
alert = AlertHistoryDB(
|
|
id=f"alert-{i}",
|
|
alert_type="task_backlog",
|
|
severity="warning",
|
|
message=f"Test alert {i}",
|
|
metric_value=50 + i,
|
|
threshold=50,
|
|
)
|
|
db_session.add(alert)
|
|
|
|
db_session.commit()
|
|
|
|
response = client.get("/api/openclaw/cron-pool/alerts")
|
|
assert response.status_code == 200
|
|
|
|
data = response.json()
|
|
assert "alerts" in data
|
|
assert len(data["alerts"]) == 3
|
|
|
|
|
|
def test_get_alert_history_unresolved_only(client: TestClient, db_session: Session):
|
|
"""Test getting unresolved alerts only."""
|
|
from agenteval.intelligent_eval.alerts import AlertHistoryDB
|
|
|
|
# Create resolved and unresolved alerts
|
|
alert1 = AlertHistoryDB(
|
|
id="alert-1",
|
|
alert_type="task_backlog",
|
|
severity="warning",
|
|
message="Test alert 1",
|
|
metric_value=60,
|
|
threshold=50,
|
|
)
|
|
alert2 = AlertHistoryDB(
|
|
id="alert-2",
|
|
alert_type="stuck_rate",
|
|
severity="critical",
|
|
message="Test alert 2",
|
|
metric_value=0.2,
|
|
threshold=0.1,
|
|
resolved_at=utc_now(),
|
|
)
|
|
|
|
db_session.add_all([alert1, alert2])
|
|
db_session.commit()
|
|
|
|
response = client.get("/api/openclaw/cron-pool/alerts?unresolved_only=true")
|
|
assert response.status_code == 200
|
|
|
|
data = response.json()
|
|
assert len(data["alerts"]) == 1
|
|
assert data["alerts"][0]["id"] == "alert-1"
|
|
|
|
|
|
def test_resolve_alert_api(client: TestClient, db_session: Session):
|
|
"""Test resolving an alert via API."""
|
|
from agenteval.intelligent_eval.alerts import AlertHistoryDB
|
|
|
|
alert = AlertHistoryDB(
|
|
id="alert-1",
|
|
alert_type="task_backlog",
|
|
severity="warning",
|
|
message="Test alert",
|
|
metric_value=60,
|
|
threshold=50,
|
|
)
|
|
db_session.add(alert)
|
|
db_session.commit()
|
|
|
|
response = client.post("/api/openclaw/cron-pool/alerts/alert-1/resolve")
|
|
assert response.status_code == 200
|
|
|
|
data = response.json()
|
|
assert data["success"] is True
|
|
|
|
# Verify alert is resolved
|
|
db_session.refresh(alert)
|
|
assert alert.resolved_at is not None
|
|
|
|
|
|
def test_resolve_nonexistent_alert_api(client: TestClient):
|
|
"""Test resolving a non-existent alert."""
|
|
response = client.post("/api/openclaw/cron-pool/alerts/nonexistent/resolve")
|
|
assert response.status_code == 404
|
|
|
|
|
|
def test_end_to_end_metrics_and_alerts(client: TestClient, db_session: Session):
|
|
"""Test end-to-end metrics and alerts flow."""
|
|
# Create high utilization state
|
|
for i in range(19):
|
|
cron = OpenClawCronPoolDB(
|
|
openclaw_cron_id=f"busy-{i}",
|
|
status="busy",
|
|
last_active_at=utc_now(),
|
|
)
|
|
db_session.add(cron)
|
|
|
|
cron = OpenClawCronPoolDB(
|
|
openclaw_cron_id="idle-0",
|
|
status="idle",
|
|
last_active_at=utc_now(),
|
|
)
|
|
db_session.add(cron)
|
|
|
|
# Create high backlog
|
|
for i in range(60):
|
|
task = IntelligentEvalTaskQueueDB(
|
|
eval_id=f"eval-{i}",
|
|
status="pending",
|
|
priority=1,
|
|
reason="slot_due",
|
|
)
|
|
db_session.add(task)
|
|
|
|
db_session.commit()
|
|
|
|
# Get metrics
|
|
response = client.get("/api/openclaw/cron-pool/metrics")
|
|
assert response.status_code == 200
|
|
metrics_data = response.json()["metrics"]
|
|
assert metrics_data["pool_utilization"] == 0.95
|
|
assert metrics_data["task_backlog"] == 60
|
|
|
|
# Check alerts
|
|
response = client.post("/api/openclaw/cron-pool/check-alerts")
|
|
assert response.status_code == 200
|
|
alerts_data = response.json()
|
|
assert alerts_data["alerts_triggered"] > 0
|
|
|
|
# Get alert history
|
|
response = client.get("/api/openclaw/cron-pool/alerts")
|
|
assert response.status_code == 200
|
|
history_data = response.json()
|
|
assert len(history_data["alerts"]) > 0
|
|
|
|
# Resolve first alert
|
|
if history_data["alerts"]:
|
|
alert_id = history_data["alerts"][0]["id"]
|
|
response = client.post(f"/api/openclaw/cron-pool/alerts/{alert_id}/resolve")
|
|
assert response.status_code == 200
|