- Create agenteval-intelligent-worker SKILL.md with decision logic
- Implement heartbeat API (POST /api/openclaw/crons/{id}/heartbeat)
- Implement decision log API (POST /api/intelligent-evals/{id}/decision-logs)
- Skill includes idle/busy state management and cron state handling
- Deployment script already syncs skills automatically
- Add 6 integration tests
All 784 tests passing.
214 lines
6.1 KiB
Python
214 lines
6.1 KiB
Python
"""Integration tests for worker skill APIs (heartbeat, decision logs)."""
|
|
|
|
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):
|
|
"""Create a TestClient with a fresh database."""
|
|
from agenteval.storage.db import ( # noqa: F401
|
|
IntelligentEvalDB,
|
|
IntelligentEvalDecisionLogDB,
|
|
OpenClawCronPoolDB,
|
|
)
|
|
|
|
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_heartbeat_idle(client: TestClient, db_session: Session):
|
|
"""Test heartbeat from idle cron."""
|
|
# Create cron
|
|
cron = OpenClawCronPoolDB(
|
|
openclaw_cron_id="cron-123",
|
|
status="idle",
|
|
last_active_at=utc_now() - timedelta(minutes=5),
|
|
)
|
|
db_session.add(cron)
|
|
db_session.commit()
|
|
|
|
old_active_at = cron.last_active_at
|
|
|
|
# Report heartbeat
|
|
response = client.post(
|
|
"/api/openclaw/crons/cron-123/heartbeat",
|
|
json={"status": "idle", "current_eval_id": None},
|
|
)
|
|
assert response.status_code == 200
|
|
assert response.json() == {"success": True}
|
|
|
|
# Verify heartbeat updated
|
|
db_session.refresh(cron)
|
|
assert cron.last_active_at > old_active_at
|
|
assert cron.status == "idle"
|
|
assert cron.current_eval_id is None
|
|
|
|
|
|
def test_heartbeat_busy(client: TestClient, db_session: Session):
|
|
"""Test heartbeat from busy cron."""
|
|
# Create cron
|
|
cron = OpenClawCronPoolDB(
|
|
openclaw_cron_id="cron-456",
|
|
status="busy",
|
|
current_eval_id="eval-789",
|
|
last_active_at=utc_now() - timedelta(minutes=2),
|
|
)
|
|
db_session.add(cron)
|
|
db_session.commit()
|
|
|
|
# Report heartbeat
|
|
response = client.post(
|
|
"/api/openclaw/crons/cron-456/heartbeat",
|
|
json={"status": "busy", "current_eval_id": "eval-789"},
|
|
)
|
|
assert response.status_code == 200
|
|
|
|
# Verify heartbeat updated
|
|
db_session.refresh(cron)
|
|
assert cron.status == "busy"
|
|
assert cron.current_eval_id == "eval-789"
|
|
|
|
|
|
def test_heartbeat_not_found(client: TestClient):
|
|
"""Test heartbeat from non-existent cron."""
|
|
response = client.post(
|
|
"/api/openclaw/crons/nonexistent/heartbeat",
|
|
json={"status": "idle", "current_eval_id": None},
|
|
)
|
|
assert response.status_code == 404
|
|
|
|
|
|
def test_create_decision_log(client: TestClient, db_session: Session):
|
|
"""Test creating a decision log."""
|
|
# Create eval
|
|
eval_db = IntelligentEvalDB(
|
|
name="test",
|
|
target_id="target1",
|
|
status=IntelligentEvalStatus.EXECUTING.value,
|
|
)
|
|
db_session.add(eval_db)
|
|
db_session.commit()
|
|
|
|
# Create decision log
|
|
response = client.post(
|
|
f"/api/intelligent-evals/{eval_db.id}/decision-logs",
|
|
json={
|
|
"decision_type": "execute_session",
|
|
"reason": "时段 8-10h 欠账 2 个会话",
|
|
"context": {
|
|
"current_slot": "8-10h",
|
|
"deficit": 2,
|
|
"completed_sessions": 1,
|
|
},
|
|
"cron_id": "cron-123",
|
|
},
|
|
)
|
|
assert response.status_code == 200
|
|
|
|
data = response.json()
|
|
assert data["eval_id"] == eval_db.id
|
|
assert data["decision_type"] == "execute_session"
|
|
assert data["reason"] == "时段 8-10h 欠账 2 个会话"
|
|
assert data["context"]["current_slot"] == "8-10h"
|
|
assert data["cron_id"] == "cron-123"
|
|
|
|
# Verify log saved to DB
|
|
log = db_session.exec(
|
|
select(IntelligentEvalDecisionLogDB).where(IntelligentEvalDecisionLogDB.eval_id == eval_db.id)
|
|
).first()
|
|
assert log is not None
|
|
assert log.decision_type == "execute_session"
|
|
|
|
|
|
def test_create_decision_log_eval_not_found(client: TestClient):
|
|
"""Test creating decision log for non-existent eval."""
|
|
response = client.post(
|
|
"/api/intelligent-evals/nonexistent/decision-logs",
|
|
json={
|
|
"decision_type": "wait",
|
|
"reason": "test",
|
|
"context": {},
|
|
"cron_id": "cron-123",
|
|
},
|
|
)
|
|
assert response.status_code == 404
|
|
|
|
|
|
def test_decision_log_multiple_entries(client: TestClient, db_session: Session):
|
|
"""Test creating multiple decision logs for same eval."""
|
|
# Create eval
|
|
eval_db = IntelligentEvalDB(
|
|
name="test",
|
|
target_id="target1",
|
|
status=IntelligentEvalStatus.EXECUTING.value,
|
|
)
|
|
db_session.add(eval_db)
|
|
db_session.commit()
|
|
|
|
# Create 3 decision logs
|
|
decisions = [
|
|
("execute_session", "时段到期"),
|
|
("wait", "当前时段无欠账"),
|
|
("start_analysis", "所有会话完成"),
|
|
]
|
|
|
|
for decision_type, reason in decisions:
|
|
response = client.post(
|
|
f"/api/intelligent-evals/{eval_db.id}/decision-logs",
|
|
json={
|
|
"decision_type": decision_type,
|
|
"reason": reason,
|
|
"context": {},
|
|
"cron_id": "cron-123",
|
|
},
|
|
)
|
|
assert response.status_code == 200
|
|
|
|
# Verify all logs saved
|
|
logs = db_session.exec(
|
|
select(IntelligentEvalDecisionLogDB)
|
|
.where(IntelligentEvalDecisionLogDB.eval_id == eval_db.id)
|
|
.order_by(IntelligentEvalDecisionLogDB.created_at)
|
|
).all()
|
|
assert len(logs) == 3
|
|
assert logs[0].decision_type == "execute_session"
|
|
assert logs[1].decision_type == "wait"
|
|
assert logs[2].decision_type == "start_analysis"
|