forked from Rockachopa/Timmy-time-dashboard
Add infrastructure for running swarm agents as isolated Docker containers with HTTP-based coordination, startup recovery, and enhanced dashboard UI for agent management. - Dockerfile and docker-compose.yml for multi-service orchestration - DockerAgentRunner for programmatic container lifecycle management - Internal HTTP API for container agents to poll tasks and submit bids - Startup recovery system to reconcile orphaned tasks and stale agents - Enhanced UI partials for agent panels, chat, and task assignment - Timmy docker entry point with heartbeat and task polling - New Makefile targets for Docker workflows - Tests for swarm recovery Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
166 lines
6.4 KiB
Python
166 lines
6.4 KiB
Python
from unittest.mock import AsyncMock, MagicMock, patch
|
|
|
|
|
|
# ── Index ─────────────────────────────────────────────────────────────────────
|
|
|
|
def test_index_returns_200(client):
|
|
response = client.get("/")
|
|
assert response.status_code == 200
|
|
|
|
|
|
def test_index_contains_title(client):
|
|
response = client.get("/")
|
|
assert "TIMMY TIME" in response.text
|
|
|
|
|
|
def test_index_contains_chat_interface(client):
|
|
response = client.get("/")
|
|
# Timmy panel loads dynamically via HTMX; verify the trigger attribute is present
|
|
assert "hx-get=\"/agents/timmy/panel\"" in response.text
|
|
|
|
|
|
# ── Health ────────────────────────────────────────────────────────────────────
|
|
|
|
def test_health_endpoint_ok(client):
|
|
with patch("dashboard.routes.health.check_ollama", new_callable=AsyncMock, return_value=True):
|
|
response = client.get("/health")
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
assert data["status"] == "ok"
|
|
assert data["services"]["ollama"] == "up"
|
|
assert "timmy" in data["agents"]
|
|
|
|
|
|
def test_health_endpoint_ollama_down(client):
|
|
with patch("dashboard.routes.health.check_ollama", new_callable=AsyncMock, return_value=False):
|
|
response = client.get("/health")
|
|
assert response.status_code == 200
|
|
assert response.json()["services"]["ollama"] == "down"
|
|
|
|
|
|
def test_health_status_panel_ollama_up(client):
|
|
with patch("dashboard.routes.health.check_ollama", new_callable=AsyncMock, return_value=True):
|
|
response = client.get("/health/status")
|
|
assert response.status_code == 200
|
|
assert "UP" in response.text
|
|
|
|
|
|
def test_health_status_panel_ollama_down(client):
|
|
with patch("dashboard.routes.health.check_ollama", new_callable=AsyncMock, return_value=False):
|
|
response = client.get("/health/status")
|
|
assert response.status_code == 200
|
|
assert "DOWN" in response.text
|
|
|
|
|
|
# ── Agents ────────────────────────────────────────────────────────────────────
|
|
|
|
def test_agents_list(client):
|
|
response = client.get("/agents")
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
assert "agents" in data
|
|
ids = [a["id"] for a in data["agents"]]
|
|
assert "timmy" in ids
|
|
|
|
|
|
def test_agents_list_timmy_metadata(client):
|
|
response = client.get("/agents")
|
|
timmy = next(a for a in response.json()["agents"] if a["id"] == "timmy")
|
|
assert timmy["name"] == "Timmy"
|
|
assert timmy["model"] == "llama3.2"
|
|
assert timmy["type"] == "sovereign"
|
|
|
|
|
|
# ── Chat ──────────────────────────────────────────────────────────────────────
|
|
|
|
def test_chat_timmy_success(client):
|
|
mock_agent = MagicMock()
|
|
mock_run = MagicMock()
|
|
mock_run.content = "I am Timmy, operational and sovereign."
|
|
mock_agent.run.return_value = mock_run
|
|
|
|
with patch("dashboard.routes.agents.create_timmy", return_value=mock_agent):
|
|
response = client.post("/agents/timmy/chat", data={"message": "status?"})
|
|
|
|
assert response.status_code == 200
|
|
assert "status?" in response.text
|
|
assert "I am Timmy" in response.text
|
|
|
|
|
|
def test_chat_timmy_shows_user_message(client):
|
|
mock_agent = MagicMock()
|
|
mock_agent.run.return_value = MagicMock(content="Acknowledged.")
|
|
|
|
with patch("dashboard.routes.agents.create_timmy", return_value=mock_agent):
|
|
response = client.post("/agents/timmy/chat", data={"message": "hello there"})
|
|
|
|
assert "hello there" in response.text
|
|
|
|
|
|
def test_chat_timmy_ollama_offline(client):
|
|
with patch("dashboard.routes.agents.create_timmy", side_effect=Exception("connection refused")):
|
|
response = client.post("/agents/timmy/chat", data={"message": "ping"})
|
|
|
|
assert response.status_code == 200
|
|
assert "Timmy is offline" in response.text
|
|
assert "ping" in response.text
|
|
|
|
|
|
def test_chat_timmy_requires_message(client):
|
|
response = client.post("/agents/timmy/chat", data={})
|
|
assert response.status_code == 422
|
|
|
|
|
|
# ── History ────────────────────────────────────────────────────────────────────
|
|
|
|
def test_history_empty_shows_init_message(client):
|
|
response = client.get("/agents/timmy/history")
|
|
assert response.status_code == 200
|
|
assert "Mission Control initialized" in response.text
|
|
|
|
|
|
def test_history_records_user_and_agent_messages(client):
|
|
mock_agent = MagicMock()
|
|
mock_agent.run.return_value = MagicMock(content="I am operational.")
|
|
|
|
with patch("dashboard.routes.agents.create_timmy", return_value=mock_agent):
|
|
client.post("/agents/timmy/chat", data={"message": "status check"})
|
|
|
|
response = client.get("/agents/timmy/history")
|
|
assert "status check" in response.text
|
|
assert "I am operational." in response.text
|
|
|
|
|
|
def test_history_records_error_when_offline(client):
|
|
with patch("dashboard.routes.agents.create_timmy", side_effect=Exception("refused")):
|
|
client.post("/agents/timmy/chat", data={"message": "ping"})
|
|
|
|
response = client.get("/agents/timmy/history")
|
|
assert "ping" in response.text
|
|
assert "Timmy is offline" in response.text
|
|
|
|
|
|
def test_history_clear_resets_to_init_message(client):
|
|
mock_agent = MagicMock()
|
|
mock_agent.run.return_value = MagicMock(content="Acknowledged.")
|
|
|
|
with patch("dashboard.routes.agents.create_timmy", return_value=mock_agent):
|
|
client.post("/agents/timmy/chat", data={"message": "hello"})
|
|
|
|
response = client.delete("/agents/timmy/history")
|
|
assert response.status_code == 200
|
|
assert "Mission Control initialized" in response.text
|
|
|
|
|
|
def test_history_empty_after_clear(client):
|
|
mock_agent = MagicMock()
|
|
mock_agent.run.return_value = MagicMock(content="OK.")
|
|
|
|
with patch("dashboard.routes.agents.create_timmy", return_value=mock_agent):
|
|
client.post("/agents/timmy/chat", data={"message": "test"})
|
|
|
|
client.delete("/agents/timmy/history")
|
|
response = client.get("/agents/timmy/history")
|
|
assert "test" not in response.text
|
|
assert "Mission Control initialized" in response.text
|