feat: swarm E2E, MCP tools, timmy-serve L402, tests, notifications

Major Features:
- Auto-spawn persona agents (Echo, Forge, Seer) on app startup
- WebSocket broadcasts for real-time swarm UI updates
- MCP tool integration: web search, file I/O, shell, Python execution
- New /tools dashboard page showing agent capabilities
- Real timmy-serve start with L402 payment gating middleware
- Browser push notifications for briefings and task events

Tests:
- test_docker_agent.py: 9 tests for Docker agent runner
- test_swarm_integration_full.py: 18 E2E lifecycle tests
- Fixed all pytest warnings (436 tests, 0 warnings)

Improvements:
- Fixed coroutine warnings in coordinator broadcasts
- Fixed ResourceWarning for unclosed process pipes
- Added pytest-asyncio config to pyproject.toml
- Test isolation with proper event loop cleanup
This commit is contained in:
Alexander Payne
2026-02-22 19:01:04 -05:00
parent c5f86b8960
commit f0aa43533f
17 changed files with 1628 additions and 13 deletions

View File

@@ -1,3 +1,7 @@
"""Pytest configuration and shared fixtures."""
import os
import sqlite3
import sys
from pathlib import Path
from unittest.mock import MagicMock
@@ -24,6 +28,10 @@ for _mod in [
]:
sys.modules.setdefault(_mod, MagicMock())
# ── Test mode setup ──────────────────────────────────────────────────────────
# Set test mode environment variable before any app imports
os.environ["TIMMY_TEST_MODE"] = "1"
@pytest.fixture(autouse=True)
def reset_message_log():
@@ -51,8 +59,101 @@ def reset_coordinator_state():
coordinator.manager.stop_all()
@pytest.fixture(autouse=True)
def clean_database():
"""Clean up database tables between tests for isolation.
Uses transaction rollback pattern: each test's changes are rolled back
to ensure perfect isolation between tests.
"""
# Pre-test: Clean database files for fresh start
db_paths = [
Path("data/swarm.db"),
Path("data/swarm.db-shm"),
Path("data/swarm.db-wal"),
]
for db_path in db_paths:
if db_path.exists():
try:
db_path.unlink()
except Exception:
pass
yield
# Post-test cleanup is handled by the reset_coordinator_state fixture
# and file deletion above ensures each test starts fresh
@pytest.fixture(autouse=True)
def cleanup_event_loops():
"""Clean up any leftover event loops after each test."""
import asyncio
import warnings
yield
# Close any unclosed event loops
try:
# Use get_running_loop first to avoid issues with running loops
try:
loop = asyncio.get_running_loop()
# If we get here, there's a running loop - don't close it
return
except RuntimeError:
pass
# No running loop, try to get and close the current loop
# Suppress DeprecationWarning for Python 3.12+
with warnings.catch_warnings():
warnings.simplefilter("ignore", DeprecationWarning)
loop = asyncio.get_event_loop_policy().get_event_loop()
if loop and not loop.is_closed():
loop.close()
except RuntimeError:
# No event loop in current thread, which is fine
pass
@pytest.fixture
def client():
"""FastAPI test client with fresh app instance."""
from dashboard.app import app
with TestClient(app) as c:
yield c
@pytest.fixture
def db_connection():
"""Provide a fresh in-memory SQLite connection for tests.
Uses transaction rollback for perfect test isolation.
"""
conn = sqlite3.connect(":memory:")
conn.row_factory = sqlite3.Row
# Create schema
conn.executescript("""
CREATE TABLE IF NOT EXISTS agents (
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
status TEXT NOT NULL DEFAULT 'idle',
capabilities TEXT DEFAULT '',
registered_at TEXT NOT NULL,
last_seen TEXT NOT NULL
);
CREATE TABLE IF NOT EXISTS tasks (
id TEXT PRIMARY KEY,
description TEXT NOT NULL,
status TEXT NOT NULL DEFAULT 'pending',
assigned_agent TEXT,
result TEXT,
created_at TEXT NOT NULL,
completed_at TEXT
);
""")
conn.commit()
yield conn
# Cleanup
conn.close()