forked from Rockachopa/Timmy-time-dashboard
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:
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user