Co-authored-by: Kimi Agent <kimi@timmy.local> Co-committed-by: Kimi Agent <kimi@timmy.local>
188 lines
6.4 KiB
Python
188 lines
6.4 KiB
Python
"""Tests for Tower dashboard route (/tower)."""
|
|
|
|
from unittest.mock import MagicMock, patch
|
|
|
|
|
|
def _mock_spark_engine():
|
|
"""Return a mock spark_engine with realistic return values."""
|
|
engine = MagicMock()
|
|
|
|
engine.status.return_value = {
|
|
"enabled": True,
|
|
"events_captured": 5,
|
|
"memories_stored": 3,
|
|
"predictions": {"total": 2, "avg_accuracy": 0.85},
|
|
"event_types": {
|
|
"task_posted": 2,
|
|
"bid_submitted": 1,
|
|
"task_assigned": 1,
|
|
"task_completed": 1,
|
|
"task_failed": 0,
|
|
"agent_joined": 0,
|
|
"tool_executed": 0,
|
|
"creative_step": 0,
|
|
},
|
|
}
|
|
|
|
event = MagicMock()
|
|
event.event_type = "task_completed"
|
|
event.description = "Task finished"
|
|
event.importance = 0.8
|
|
event.created_at = "2026-01-01T00:00:00"
|
|
event.agent_id = "agent-1234-abcd"
|
|
event.task_id = "task-5678-efgh"
|
|
event.data = '{"result": "ok"}'
|
|
engine.get_timeline.return_value = [event]
|
|
|
|
pred = MagicMock()
|
|
pred.task_id = "task-5678-efgh"
|
|
pred.accuracy = 0.9
|
|
pred.evaluated_at = "2026-01-01T01:00:00"
|
|
pred.created_at = "2026-01-01T00:30:00"
|
|
pred.predicted_value = '{"outcome": "success"}'
|
|
engine.get_predictions.return_value = [pred]
|
|
|
|
advisory = MagicMock()
|
|
advisory.category = "performance"
|
|
advisory.priority = "high"
|
|
advisory.title = "Slow tasks"
|
|
advisory.detail = "Tasks taking longer than expected"
|
|
advisory.suggested_action = "Scale up workers"
|
|
engine.get_advisories.return_value = [advisory]
|
|
|
|
return engine
|
|
|
|
|
|
class TestTowerUI:
|
|
"""Tests for GET /tower endpoint."""
|
|
|
|
@patch("dashboard.routes.tower.spark_engine", new_callable=_mock_spark_engine)
|
|
def test_tower_returns_200(self, mock_engine, client):
|
|
response = client.get("/tower")
|
|
assert response.status_code == 200
|
|
|
|
@patch("dashboard.routes.tower.spark_engine", new_callable=_mock_spark_engine)
|
|
def test_tower_returns_html(self, mock_engine, client):
|
|
response = client.get("/tower")
|
|
assert "text/html" in response.headers["content-type"]
|
|
|
|
@patch("dashboard.routes.tower.spark_engine", new_callable=_mock_spark_engine)
|
|
def test_tower_contains_dashboard_content(self, mock_engine, client):
|
|
response = client.get("/tower")
|
|
body = response.text
|
|
assert "tower" in body.lower() or "spark" in body.lower()
|
|
|
|
|
|
class TestSparkSnapshot:
|
|
"""Tests for _spark_snapshot helper."""
|
|
|
|
@patch("dashboard.routes.tower.spark_engine", new_callable=_mock_spark_engine)
|
|
def test_snapshot_structure(self, mock_engine):
|
|
from dashboard.routes.tower import _spark_snapshot
|
|
|
|
snap = _spark_snapshot()
|
|
assert snap["type"] == "spark_state"
|
|
assert "status" in snap
|
|
assert "events" in snap
|
|
assert "predictions" in snap
|
|
assert "advisories" in snap
|
|
|
|
@patch("dashboard.routes.tower.spark_engine", new_callable=_mock_spark_engine)
|
|
def test_snapshot_events_parsed(self, mock_engine):
|
|
from dashboard.routes.tower import _spark_snapshot
|
|
|
|
snap = _spark_snapshot()
|
|
ev = snap["events"][0]
|
|
assert ev["event_type"] == "task_completed"
|
|
assert ev["importance"] == 0.8
|
|
assert ev["agent_id"] == "agent-12"
|
|
assert ev["task_id"] == "task-567"
|
|
assert ev["data"] == {"result": "ok"}
|
|
|
|
@patch("dashboard.routes.tower.spark_engine", new_callable=_mock_spark_engine)
|
|
def test_snapshot_predictions_parsed(self, mock_engine):
|
|
from dashboard.routes.tower import _spark_snapshot
|
|
|
|
snap = _spark_snapshot()
|
|
pred = snap["predictions"][0]
|
|
assert pred["task_id"] == "task-567"
|
|
assert pred["accuracy"] == 0.9
|
|
assert pred["evaluated"] is True
|
|
assert pred["predicted"] == {"outcome": "success"}
|
|
|
|
@patch("dashboard.routes.tower.spark_engine", new_callable=_mock_spark_engine)
|
|
def test_snapshot_advisories_parsed(self, mock_engine):
|
|
from dashboard.routes.tower import _spark_snapshot
|
|
|
|
snap = _spark_snapshot()
|
|
adv = snap["advisories"][0]
|
|
assert adv["category"] == "performance"
|
|
assert adv["priority"] == "high"
|
|
assert adv["title"] == "Slow tasks"
|
|
assert adv["suggested_action"] == "Scale up workers"
|
|
|
|
@patch("dashboard.routes.tower.spark_engine")
|
|
def test_snapshot_handles_empty_state(self, mock_engine):
|
|
mock_engine.status.return_value = {"enabled": False}
|
|
mock_engine.get_timeline.return_value = []
|
|
mock_engine.get_predictions.return_value = []
|
|
mock_engine.get_advisories.return_value = []
|
|
|
|
from dashboard.routes.tower import _spark_snapshot
|
|
|
|
snap = _spark_snapshot()
|
|
assert snap["events"] == []
|
|
assert snap["predictions"] == []
|
|
assert snap["advisories"] == []
|
|
|
|
@patch("dashboard.routes.tower.spark_engine")
|
|
def test_snapshot_handles_invalid_json_data(self, mock_engine):
|
|
mock_engine.status.return_value = {"enabled": True}
|
|
|
|
event = MagicMock()
|
|
event.event_type = "test"
|
|
event.description = "bad data"
|
|
event.importance = 0.5
|
|
event.created_at = "2026-01-01T00:00:00"
|
|
event.agent_id = None
|
|
event.task_id = None
|
|
event.data = "not-json{"
|
|
mock_engine.get_timeline.return_value = [event]
|
|
|
|
pred = MagicMock()
|
|
pred.task_id = None
|
|
pred.accuracy = None
|
|
pred.evaluated_at = None
|
|
pred.created_at = "2026-01-01T00:00:00"
|
|
pred.predicted_value = None
|
|
mock_engine.get_predictions.return_value = [pred]
|
|
|
|
mock_engine.get_advisories.return_value = []
|
|
|
|
from dashboard.routes.tower import _spark_snapshot
|
|
|
|
snap = _spark_snapshot()
|
|
ev = snap["events"][0]
|
|
assert ev["data"] == {}
|
|
assert "agent_id" not in ev
|
|
assert "task_id" not in ev
|
|
|
|
pred = snap["predictions"][0]
|
|
assert pred["task_id"] == "?"
|
|
assert pred["predicted"] == {}
|
|
|
|
|
|
class TestTowerWebSocket:
|
|
"""Tests for WS /tower/ws endpoint."""
|
|
|
|
@patch("dashboard.routes.tower.spark_engine", new_callable=_mock_spark_engine)
|
|
@patch("dashboard.routes.tower._PUSH_INTERVAL", 0)
|
|
def test_ws_sends_initial_snapshot(self, mock_engine, client):
|
|
import json
|
|
|
|
with client.websocket_connect("/tower/ws") as ws:
|
|
data = json.loads(ws.receive_text())
|
|
assert data["type"] == "spark_state"
|
|
assert "status" in data
|
|
assert "events" in data
|