mirror of
https://github.com/sstent/AICycling_mcp.git
synced 2026-02-01 12:01:36 +00:00
restrcuted repo
This commit is contained in:
0
tests/__init__.py
Normal file
0
tests/__init__.py
Normal file
195
tests/minimal_mcp_test.py
Normal file
195
tests/minimal_mcp_test.py
Normal file
@@ -0,0 +1,195 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Minimal MCP Test - Just test MCP connection and user profile
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import yaml
|
||||
from pathlib import Path
|
||||
|
||||
# Minimal imports - just what we need
|
||||
try:
|
||||
from pydantic_ai.mcp import MCPServerStdio
|
||||
import shutil
|
||||
MCP_AVAILABLE = True
|
||||
except ImportError:
|
||||
print("❌ pydantic-ai MCP not available")
|
||||
print("Install with: pip install pydantic-ai")
|
||||
exit(1)
|
||||
|
||||
# Simple logging
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class MinimalMCPTest:
|
||||
"""Minimal MCP test class"""
|
||||
|
||||
def __init__(self, garth_token: str, server_path: str = "uvx"):
|
||||
self.garth_token = garth_token
|
||||
self.server_path = server_path
|
||||
self.mcp_server = None
|
||||
self.cached_profile = None
|
||||
|
||||
def setup_mcp_server(self):
|
||||
"""Setup MCP server connection"""
|
||||
# Set environment
|
||||
os.environ["GARTH_TOKEN"] = self.garth_token
|
||||
env = os.environ.copy()
|
||||
|
||||
# Find server executable
|
||||
server_executable = shutil.which(self.server_path)
|
||||
if not server_executable:
|
||||
raise FileNotFoundError(f"'{self.server_path}' not found in PATH")
|
||||
|
||||
self.mcp_server = MCPServerStdio(
|
||||
command=server_executable,
|
||||
args=["garth-mcp-server"],
|
||||
env=env,
|
||||
)
|
||||
|
||||
print("✅ MCP server configured")
|
||||
|
||||
async def test_connection(self):
|
||||
"""Test basic MCP connection"""
|
||||
if not self.mcp_server:
|
||||
raise RuntimeError("MCP server not configured")
|
||||
|
||||
try:
|
||||
# Try to list tools
|
||||
tools = await self.mcp_server.list_tools()
|
||||
print(f"✅ MCP connected - found {len(tools)} tools")
|
||||
|
||||
# Show tools
|
||||
for tool in tools:
|
||||
print(f" 📋 {tool.name}: {getattr(tool, 'description', 'No description')}")
|
||||
|
||||
return tools
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ MCP connection failed: {e}")
|
||||
raise
|
||||
|
||||
async def get_user_profile(self):
|
||||
"""Get and cache user profile"""
|
||||
try:
|
||||
print("📞 Calling user_profile tool...")
|
||||
|
||||
# Direct tool call
|
||||
result = await self.mcp_server.direct_call_tool("user_profile", {})
|
||||
profile_data = result.output if hasattr(result, 'output') else result
|
||||
|
||||
# Cache it
|
||||
self.cached_profile = profile_data
|
||||
|
||||
print("✅ User profile retrieved and cached")
|
||||
return profile_data
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ Failed to get user profile: {e}")
|
||||
raise
|
||||
|
||||
def print_profile(self):
|
||||
"""Print cached profile"""
|
||||
if not self.cached_profile:
|
||||
print("❌ No cached profile")
|
||||
return
|
||||
|
||||
print("\n" + "="*50)
|
||||
print("USER PROFILE")
|
||||
print("="*50)
|
||||
print(json.dumps(self.cached_profile, indent=2, default=str))
|
||||
print("="*50)
|
||||
|
||||
async def run_test(self):
|
||||
"""Run the complete test"""
|
||||
print("🚀 Starting Minimal MCP Test\n")
|
||||
|
||||
# Setup
|
||||
self.setup_mcp_server()
|
||||
|
||||
# Test connection
|
||||
tools = await self.test_connection()
|
||||
|
||||
# Check if user_profile tool exists
|
||||
user_profile_tool = next((t for t in tools if t.name == "user_profile"), None)
|
||||
if not user_profile_tool:
|
||||
print("❌ user_profile tool not found")
|
||||
return False
|
||||
|
||||
# Get user profile
|
||||
await self.get_user_profile()
|
||||
|
||||
# Show results
|
||||
self.print_profile()
|
||||
|
||||
print("\n🎉 Test completed successfully!")
|
||||
return True
|
||||
|
||||
def get_config():
|
||||
"""Get configuration from config.yaml or environment"""
|
||||
config_file = Path("config.yaml")
|
||||
|
||||
# Try to load from config.yaml first
|
||||
if config_file.exists():
|
||||
try:
|
||||
with open(config_file, 'r') as f:
|
||||
config_data = yaml.safe_load(f)
|
||||
|
||||
garth_token = config_data.get("garth_token")
|
||||
server_path = config_data.get("garth_mcp_server_path", "uvx")
|
||||
|
||||
if garth_token and garth_token != "your_garth_token_here":
|
||||
print("✅ Configuration loaded from config.yaml")
|
||||
return garth_token, server_path
|
||||
else:
|
||||
print("⚠️ garth_token not properly set in config.yaml")
|
||||
|
||||
except yaml.YAMLError as e:
|
||||
print(f"❌ Error parsing config.yaml: {e}")
|
||||
except Exception as e:
|
||||
print(f"❌ Error reading config.yaml: {e}")
|
||||
else:
|
||||
print("ℹ️ config.yaml not found")
|
||||
|
||||
# Fallback to environment variables
|
||||
print("Trying environment variables...")
|
||||
garth_token = os.getenv("GARTH_TOKEN")
|
||||
|
||||
if not garth_token:
|
||||
print("❌ GARTH_TOKEN not found in config.yaml or environment")
|
||||
print("Please either:")
|
||||
print("1. Create config.yaml with garth_token")
|
||||
print("2. Run 'uvx garth login' and set GARTH_TOKEN environment variable")
|
||||
raise ValueError("GARTH_TOKEN is required")
|
||||
|
||||
server_path = os.getenv("GARTH_MCP_SERVER_PATH", "uvx")
|
||||
print("✅ Configuration loaded from environment variables")
|
||||
|
||||
return garth_token, server_path
|
||||
|
||||
async def main():
|
||||
"""Main entry point"""
|
||||
try:
|
||||
# Get config
|
||||
garth_token, server_path = get_config()
|
||||
|
||||
# Run test
|
||||
test = MinimalMCPTest(garth_token, server_path)
|
||||
success = await test.run_test()
|
||||
|
||||
if success:
|
||||
print("\n✅ All tests passed!")
|
||||
else:
|
||||
print("\n❌ Tests failed!")
|
||||
|
||||
except KeyboardInterrupt:
|
||||
print("\n👋 Interrupted by user")
|
||||
except Exception as e:
|
||||
print(f"\n💥 Error: {e}")
|
||||
logger.error("Test error", exc_info=True)
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
63
tests/simple_garth_test.py
Normal file
63
tests/simple_garth_test.py
Normal file
@@ -0,0 +1,63 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Simple Garth Test - Minimal test to verify Garth is working
|
||||
"""
|
||||
|
||||
import garth
|
||||
from pathlib import Path
|
||||
|
||||
def simple_test():
|
||||
print("🔧 Simple Garth Connection Test")
|
||||
print("=" * 40)
|
||||
|
||||
# Check if session exists
|
||||
session_path = Path.home() / ".garth"
|
||||
if session_path.exists():
|
||||
print("✅ Session file exists")
|
||||
try:
|
||||
garth.resume(str(session_path))
|
||||
print("✅ Session loaded")
|
||||
except Exception as e:
|
||||
print(f"❌ Session load failed: {e}")
|
||||
return False
|
||||
else:
|
||||
print("❌ No session file found")
|
||||
return False
|
||||
|
||||
# Try the simplest possible API call
|
||||
simple_endpoints = [
|
||||
"/",
|
||||
"/ping",
|
||||
"/health",
|
||||
"/status"
|
||||
]
|
||||
|
||||
print("\n🧪 Testing simple endpoints...")
|
||||
for endpoint in simple_endpoints:
|
||||
try:
|
||||
result = garth.connectapi(endpoint)
|
||||
print(f"✅ {endpoint} worked: {type(result)}")
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"❌ {endpoint} failed: {str(e)[:50]}")
|
||||
|
||||
# Try to access the raw client
|
||||
print("\n🔍 Checking Garth client details...")
|
||||
try:
|
||||
client = garth.client
|
||||
print(f"Client type: {type(client)}")
|
||||
print(f"Client attributes: {[attr for attr in dir(client) if not attr.startswith('_')][:5]}")
|
||||
|
||||
# Try to see if we can get base URL
|
||||
if hasattr(client, 'domain'):
|
||||
print(f"Domain: {client.domain}")
|
||||
if hasattr(client, 'base_url'):
|
||||
print(f"Base URL: {client.base_url}")
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ Client inspection failed: {e}")
|
||||
|
||||
return False
|
||||
|
||||
if __name__ == "__main__":
|
||||
simple_test()
|
||||
204
tests/standalone_test.py
Normal file
204
tests/standalone_test.py
Normal file
@@ -0,0 +1,204 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Standalone MCP Test - Single file test for MCP connection and user profile
|
||||
No external dependencies on the modular architecture - just tests MCP directly
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import json
|
||||
import os
|
||||
import shutil
|
||||
import yaml
|
||||
from pathlib import Path
|
||||
|
||||
# Check dependencies
|
||||
try:
|
||||
from pydantic_ai.mcp import MCPServerStdio
|
||||
print("✅ pydantic-ai MCP available")
|
||||
except ImportError:
|
||||
print("❌ pydantic-ai MCP not available")
|
||||
print("Install with: pip install pydantic-ai")
|
||||
exit(1)
|
||||
|
||||
def load_config_from_yaml():
|
||||
"""Load configuration from config.yaml file"""
|
||||
config_file = Path("config.yaml")
|
||||
|
||||
if not config_file.exists():
|
||||
print("❌ config.yaml not found")
|
||||
print("Please create config.yaml with your settings:")
|
||||
print("""
|
||||
garth_token: "your_garth_token_here"
|
||||
openrouter_api_key: "your_openrouter_api_key_here"
|
||||
openrouter_model: "deepseek/deepseek-chat-v3.1"
|
||||
garth_mcp_server_path: "uvx"
|
||||
""")
|
||||
return None
|
||||
|
||||
try:
|
||||
with open(config_file, 'r') as f:
|
||||
config_data = yaml.safe_load(f)
|
||||
|
||||
print(f"✅ Loaded config from {config_file}")
|
||||
return config_data
|
||||
|
||||
except yaml.YAMLError as e:
|
||||
print(f"❌ Error parsing config.yaml: {e}")
|
||||
return None
|
||||
except Exception as e:
|
||||
print(f"❌ Error reading config.yaml: {e}")
|
||||
return None
|
||||
|
||||
async def test_mcp_user_profile():
|
||||
"""Simple test to connect to MCP and get user profile"""
|
||||
|
||||
print("🚀 MCP User Profile Test")
|
||||
print("=" * 40)
|
||||
|
||||
# 1. Load configuration from config.yaml
|
||||
config = load_config_from_yaml()
|
||||
if not config:
|
||||
return False
|
||||
|
||||
# 2. Get garth_token from config
|
||||
garth_token = config.get("garth_token")
|
||||
if not garth_token or garth_token == "your_garth_token_here":
|
||||
print("❌ garth_token not properly set in config.yaml")
|
||||
print("Please run: uvx garth login")
|
||||
print("Then update config.yaml with your token")
|
||||
return False
|
||||
|
||||
print("✅ GARTH_TOKEN loaded from config.yaml")
|
||||
|
||||
# 3. Get server path from config
|
||||
server_path = config.get("garth_mcp_server_path", "uvx")
|
||||
server_executable = shutil.which(server_path)
|
||||
if not server_executable:
|
||||
print(f"❌ {server_path} not found")
|
||||
print("Please install uvx and garth-mcp-server")
|
||||
return False
|
||||
|
||||
print(f"✅ {server_path} found")
|
||||
|
||||
# 4. Setup MCP server
|
||||
print("🔧 Setting up MCP server...")
|
||||
|
||||
env = os.environ.copy()
|
||||
env["GARTH_TOKEN"] = garth_token
|
||||
|
||||
mcp_server = MCPServerStdio(
|
||||
command=server_executable,
|
||||
args=["garth-mcp-server"],
|
||||
env=env,
|
||||
)
|
||||
|
||||
try:
|
||||
# 5. List available tools
|
||||
print("📋 Listing MCP tools...")
|
||||
tools = await mcp_server.list_tools()
|
||||
|
||||
print(f"Found {len(tools)} tools:")
|
||||
for tool in tools:
|
||||
print(f" • {tool.name}")
|
||||
|
||||
# 6. Check for user_profile tool
|
||||
user_profile_tool = next((t for t in tools if t.name == "user_profile"), None)
|
||||
if not user_profile_tool:
|
||||
print("❌ user_profile tool not available")
|
||||
return False
|
||||
|
||||
print("✅ user_profile tool found")
|
||||
|
||||
# 7. Call user_profile tool
|
||||
print("📞 Getting user profile...")
|
||||
result = await mcp_server.direct_call_tool("user_profile", {})
|
||||
|
||||
# Extract data
|
||||
profile_data = result.output if hasattr(result, 'output') else result
|
||||
|
||||
# 8. Display results
|
||||
print("\n" + "=" * 50)
|
||||
print("USER PROFILE RETRIEVED")
|
||||
print("=" * 50)
|
||||
print(json.dumps(profile_data, indent=2, default=str))
|
||||
print("=" * 50)
|
||||
|
||||
# 9. Quick analysis
|
||||
if isinstance(profile_data, dict):
|
||||
print(f"\n📊 Profile contains {len(profile_data)} fields:")
|
||||
for key in list(profile_data.keys())[:5]: # Show first 5 keys
|
||||
print(f" • {key}")
|
||||
if len(profile_data) > 5:
|
||||
print(f" ... and {len(profile_data) - 5} more")
|
||||
|
||||
print("\n🎉 Test completed successfully!")
|
||||
|
||||
# 10. Show config info used
|
||||
print(f"\n📝 Configuration used:")
|
||||
print(f" • Model: {config.get('openrouter_model', 'Not set')}")
|
||||
print(f" • OpenRouter API Key: {'Set' if config.get('openrouter_api_key') else 'Not set'}")
|
||||
print(f" • Server Path: {server_path}")
|
||||
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ Error during test: {e}")
|
||||
print(f"Error type: {type(e).__name__}")
|
||||
return False
|
||||
|
||||
async def main():
|
||||
"""Run the test"""
|
||||
try:
|
||||
success = await test_mcp_user_profile()
|
||||
if success:
|
||||
print("\n✅ MCP user profile test PASSED")
|
||||
else:
|
||||
print("\n❌ MCP user profile test FAILED")
|
||||
|
||||
except KeyboardInterrupt:
|
||||
print("\n👋 Test interrupted")
|
||||
except Exception as e:
|
||||
print(f"\n💥 Unexpected error: {e}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
print("Standalone MCP User Profile Test")
|
||||
print("This will test MCP connection and retrieve your Garmin user profile")
|
||||
print()
|
||||
|
||||
# Check prerequisites
|
||||
print("Prerequisites check:")
|
||||
|
||||
# Check if config.yaml exists
|
||||
config_file = Path("config.yaml")
|
||||
if config_file.exists():
|
||||
print("✅ config.yaml found")
|
||||
try:
|
||||
with open(config_file, 'r') as f:
|
||||
config = yaml.safe_load(f)
|
||||
|
||||
# Check garth_token in config
|
||||
if config.get("garth_token") and config.get("garth_token") != "your_garth_token_here":
|
||||
print("✅ garth_token set in config.yaml")
|
||||
else:
|
||||
print("❌ garth_token not properly set in config.yaml")
|
||||
|
||||
# Check openrouter_api_key
|
||||
if config.get("openrouter_api_key") and config.get("openrouter_api_key") != "your_openrouter_api_key_here":
|
||||
print("✅ openrouter_api_key set in config.yaml")
|
||||
else:
|
||||
print("❌ openrouter_api_key not set in config.yaml")
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ Error reading config.yaml: {e}")
|
||||
else:
|
||||
print("❌ config.yaml not found")
|
||||
|
||||
if shutil.which("uvx"):
|
||||
print("✅ uvx command available")
|
||||
else:
|
||||
print("❌ uvx not found - install it first")
|
||||
|
||||
print()
|
||||
|
||||
# Run the test
|
||||
asyncio.run(main())
|
||||
445
tests/test_custom_mcp.py
Normal file
445
tests/test_custom_mcp.py
Normal file
@@ -0,0 +1,445 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Test Custom MCP Implementation
|
||||
Test our custom Garth MCP wrapper
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import json
|
||||
import logging
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
# Import our modules
|
||||
from config import Config, load_config, create_sample_config
|
||||
from mcp_client import MCPClient # Updated MCP client
|
||||
from cache_manager import CacheManager
|
||||
|
||||
# Configure logging
|
||||
logging.basicConfig(
|
||||
level=logging.INFO,
|
||||
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
|
||||
)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class CustomMCPTestApp:
|
||||
"""Test application for custom MCP implementation"""
|
||||
|
||||
def __init__(self, config: Config):
|
||||
self.config = config
|
||||
self.mcp_client = MCPClient(config) # Will use custom implementation
|
||||
|
||||
async def initialize(self):
|
||||
"""Initialize MCP client"""
|
||||
logger.info("Initializing Custom MCP test app...")
|
||||
await self.mcp_client.initialize()
|
||||
|
||||
async def cleanup(self):
|
||||
"""Cleanup resources"""
|
||||
await self.mcp_client.cleanup()
|
||||
|
||||
async def test_connection_and_tools(self):
|
||||
"""Test basic connection and list tools"""
|
||||
print("\n" + "="*60)
|
||||
print("CUSTOM MCP CONNECTION TEST")
|
||||
print("="*60)
|
||||
|
||||
if not self.mcp_client.is_available:
|
||||
print("❌ Custom MCP not available")
|
||||
return False
|
||||
|
||||
print("✅ Custom MCP connected")
|
||||
print(f"📋 Implementation: {self.mcp_client.implementation_type}")
|
||||
|
||||
# List available tools
|
||||
tools = await self.mcp_client.list_tools()
|
||||
print(f"📋 Found {len(tools)} tools")
|
||||
|
||||
if tools:
|
||||
print("\nAvailable tools:")
|
||||
for i, tool in enumerate(tools[:10], 1): # Show first 10
|
||||
print(f" {i:2d}. {tool.name}")
|
||||
if hasattr(tool, 'description'):
|
||||
print(f" {tool.description}")
|
||||
else:
|
||||
print(" No tools available")
|
||||
|
||||
return len(tools) > 0
|
||||
|
||||
async def test_user_profile(self):
|
||||
"""Test user profile retrieval"""
|
||||
print("\n" + "="*60)
|
||||
print("USER PROFILE TEST")
|
||||
print("="*60)
|
||||
|
||||
# Check if tool exists
|
||||
if not await self.mcp_client.has_tool("user_profile"):
|
||||
print("❌ user_profile tool not available")
|
||||
return None
|
||||
|
||||
print("✅ user_profile tool found")
|
||||
|
||||
try:
|
||||
# Call user_profile tool
|
||||
print("📞 Calling user_profile tool...")
|
||||
profile_data = await self.mcp_client.call_tool("user_profile", {})
|
||||
|
||||
print("✅ User profile retrieved")
|
||||
|
||||
# Show profile summary
|
||||
if isinstance(profile_data, dict):
|
||||
display_name = profile_data.get('displayName', 'Unknown')
|
||||
full_name = profile_data.get('fullName', 'Unknown')
|
||||
user_name = profile_data.get('userName', 'Unknown')
|
||||
|
||||
print(f" Display Name: {display_name}")
|
||||
print(f" Full Name: {full_name}")
|
||||
print(f" Username: {user_name}")
|
||||
print(f" Profile contains {len(profile_data)} fields")
|
||||
|
||||
return profile_data
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ Error getting user profile: {e}")
|
||||
logger.error(f"User profile error: {e}", exc_info=True)
|
||||
return None
|
||||
|
||||
async def test_activities(self):
|
||||
"""Test activities retrieval"""
|
||||
print("\n" + "="*60)
|
||||
print("ACTIVITIES TEST")
|
||||
print("="*60)
|
||||
|
||||
if not await self.mcp_client.has_tool("get_activities"):
|
||||
print("❌ get_activities tool not available")
|
||||
return None
|
||||
|
||||
print("✅ get_activities tool found")
|
||||
|
||||
try:
|
||||
print("📞 Calling get_activities (limit=5)...")
|
||||
activities = await self.mcp_client.call_tool("get_activities", {"limit": 5})
|
||||
|
||||
if activities and len(activities) > 0:
|
||||
print(f"✅ Retrieved {len(activities)} activities")
|
||||
|
||||
# Show activity summaries
|
||||
print("\nRecent Activities:")
|
||||
for i, activity in enumerate(activities[:3], 1):
|
||||
activity_id = activity.get('activityId', 'Unknown')
|
||||
activity_type = activity.get('activityType', {})
|
||||
type_key = activity_type.get('typeKey', 'Unknown') if isinstance(activity_type, dict) else str(activity_type)
|
||||
start_time = activity.get('startTimeLocal', 'Unknown time')
|
||||
|
||||
print(f" {i}. {type_key} (ID: {activity_id})")
|
||||
print(f" Start: {start_time}")
|
||||
|
||||
# Try to get duration
|
||||
duration = activity.get('duration')
|
||||
if duration:
|
||||
minutes = duration // 60
|
||||
seconds = duration % 60
|
||||
print(f" Duration: {minutes}m {seconds}s")
|
||||
|
||||
return activities
|
||||
else:
|
||||
print("📋 No activities found")
|
||||
return []
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ Error getting activities: {e}")
|
||||
logger.error(f"Activities error: {e}", exc_info=True)
|
||||
return None
|
||||
|
||||
async def test_activity_details(self, activities):
|
||||
"""Test activity details retrieval"""
|
||||
print("\n" + "="*60)
|
||||
print("ACTIVITY DETAILS TEST")
|
||||
print("="*60)
|
||||
|
||||
if not activities or len(activities) == 0:
|
||||
print("❌ No activities available for details test")
|
||||
return None
|
||||
|
||||
if not await self.mcp_client.has_tool("get_activity_details"):
|
||||
print("❌ get_activity_details tool not available")
|
||||
return None
|
||||
|
||||
# Get details for first activity
|
||||
first_activity = activities[0]
|
||||
activity_id = str(first_activity.get('activityId'))
|
||||
|
||||
print(f"📞 Getting details for activity {activity_id}...")
|
||||
|
||||
try:
|
||||
details = await self.mcp_client.call_tool("get_activity_details", {
|
||||
"activity_id": activity_id
|
||||
})
|
||||
|
||||
print("✅ Activity details retrieved")
|
||||
|
||||
if isinstance(details, dict):
|
||||
print(f" Details contain {len(details)} fields")
|
||||
|
||||
# Show some key details
|
||||
activity_name = details.get('activityName', 'Unnamed')
|
||||
sport = details.get('sportTypeId', 'Unknown sport')
|
||||
distance = details.get('distance')
|
||||
elapsed_duration = details.get('elapsedDuration')
|
||||
|
||||
print(f" Activity: {activity_name}")
|
||||
print(f" Sport: {sport}")
|
||||
if distance:
|
||||
print(f" Distance: {distance/1000:.2f} km")
|
||||
if elapsed_duration:
|
||||
minutes = elapsed_duration // 60
|
||||
seconds = elapsed_duration % 60
|
||||
print(f" Duration: {minutes}m {seconds}s")
|
||||
|
||||
return details
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ Error getting activity details: {e}")
|
||||
logger.error(f"Activity details error: {e}", exc_info=True)
|
||||
return None
|
||||
|
||||
async def test_daily_metrics(self):
|
||||
"""Test daily metrics retrieval"""
|
||||
print("\n" + "="*60)
|
||||
print("DAILY METRICS TEST")
|
||||
print("="*60)
|
||||
|
||||
metrics_to_test = ["daily_steps", "daily_sleep", "daily_stress"]
|
||||
results = {}
|
||||
|
||||
for metric in metrics_to_test:
|
||||
if await self.mcp_client.has_tool(metric):
|
||||
try:
|
||||
print(f"📞 Testing {metric}...")
|
||||
data = await self.mcp_client.call_tool(metric, {"days": 1})
|
||||
|
||||
if data:
|
||||
print(f"✅ {metric} data retrieved")
|
||||
if isinstance(data, list) and len(data) > 0:
|
||||
print(f" Contains {len(data)} day(s) of data")
|
||||
results[metric] = data
|
||||
else:
|
||||
print(f"⚠️ {metric} returned no data")
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ Error with {metric}: {e}")
|
||||
logger.debug(f"{metric} error: {e}")
|
||||
else:
|
||||
print(f"❌ {metric} tool not available")
|
||||
|
||||
return results
|
||||
|
||||
async def test_cache_functionality(self):
|
||||
"""Test cache functionality"""
|
||||
print("\n" + "="*60)
|
||||
print("CACHE FUNCTIONALITY TEST")
|
||||
print("="*60)
|
||||
|
||||
if self.mcp_client.implementation_type != "custom_garth":
|
||||
print("❌ Cache testing only available with custom implementation")
|
||||
return False
|
||||
|
||||
try:
|
||||
# Call user_profile twice to test caching
|
||||
print("📞 First user_profile call (should hit API)...")
|
||||
profile1 = await self.mcp_client.call_tool("user_profile", {})
|
||||
|
||||
print("📞 Second user_profile call (should hit cache)...")
|
||||
profile2 = await self.mcp_client.call_tool("user_profile", {})
|
||||
|
||||
# Verify same data
|
||||
if profile1 == profile2:
|
||||
print("✅ Cache working correctly")
|
||||
else:
|
||||
print("⚠️ Cache data differs from API data")
|
||||
|
||||
# Show cache stats
|
||||
cache_stats = self.mcp_client.get_cache_stats()
|
||||
print(f"📊 Cache stats: {cache_stats}")
|
||||
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ Cache test failed: {e}")
|
||||
logger.error(f"Cache test error: {e}", exc_info=True)
|
||||
return False
|
||||
|
||||
async def test_comprehensive_snapshot(self):
|
||||
"""Test comprehensive data snapshot"""
|
||||
print("\n" + "="*60)
|
||||
print("COMPREHENSIVE SNAPSHOT TEST")
|
||||
print("="*60)
|
||||
|
||||
if not await self.mcp_client.has_tool("snapshot"):
|
||||
print("❌ snapshot tool not available")
|
||||
return None
|
||||
|
||||
try:
|
||||
print("📞 Getting comprehensive data snapshot...")
|
||||
snapshot = await self.mcp_client.call_tool("snapshot", {
|
||||
"start_date": "2024-09-20",
|
||||
"end_date": "2024-09-24"
|
||||
})
|
||||
|
||||
if isinstance(snapshot, dict):
|
||||
print("✅ Snapshot retrieved successfully")
|
||||
print(f" Snapshot contains {len(snapshot)} data categories:")
|
||||
|
||||
for category, data in snapshot.items():
|
||||
if isinstance(data, list):
|
||||
print(f" - {category}: {len(data)} items")
|
||||
elif isinstance(data, dict):
|
||||
print(f" - {category}: {len(data)} fields")
|
||||
else:
|
||||
print(f" - {category}: {type(data).__name__}")
|
||||
|
||||
return snapshot
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ Snapshot test failed: {e}")
|
||||
logger.error(f"Snapshot error: {e}", exc_info=True)
|
||||
return None
|
||||
|
||||
async def run_all_tests(self):
|
||||
"""Run comprehensive test suite"""
|
||||
print("🚀 Starting Custom MCP Test Suite")
|
||||
|
||||
results = {}
|
||||
|
||||
# Test 1: Connection and tools
|
||||
results['connection'] = await self.test_connection_and_tools()
|
||||
|
||||
if not results['connection']:
|
||||
print("\n❌ Connection failed - skipping remaining tests")
|
||||
return results
|
||||
|
||||
# Test 2: User profile
|
||||
profile = await self.test_user_profile()
|
||||
results['user_profile'] = profile is not None
|
||||
|
||||
# Test 3: Activities
|
||||
activities = await self.test_activities()
|
||||
results['activities'] = activities is not None
|
||||
|
||||
# Test 4: Activity details (if we have activities)
|
||||
if activities and len(activities) > 0:
|
||||
details = await self.test_activity_details(activities)
|
||||
results['activity_details'] = details is not None
|
||||
|
||||
# Test 5: Daily metrics
|
||||
metrics = await self.test_daily_metrics()
|
||||
results['daily_metrics'] = len(metrics) > 0
|
||||
|
||||
# Test 6: Cache functionality
|
||||
results['cache'] = await self.test_cache_functionality()
|
||||
|
||||
# Test 7: Comprehensive snapshot
|
||||
snapshot = await self.test_comprehensive_snapshot()
|
||||
results['snapshot'] = snapshot is not None
|
||||
|
||||
# Summary
|
||||
self._print_test_summary(results)
|
||||
|
||||
return results
|
||||
|
||||
def _print_test_summary(self, results):
|
||||
"""Print test summary"""
|
||||
print("\n" + "="*60)
|
||||
print("TEST SUMMARY")
|
||||
print("="*60)
|
||||
|
||||
passed = 0
|
||||
total = 0
|
||||
|
||||
for test_name, result in results.items():
|
||||
total += 1
|
||||
if result:
|
||||
passed += 1
|
||||
status = "✅ PASS"
|
||||
else:
|
||||
status = "❌ FAIL"
|
||||
|
||||
print(f"{test_name.replace('_', ' ').title():.<40} {status}")
|
||||
|
||||
print("-" * 60)
|
||||
print(f"Total: {passed}/{total} tests passed")
|
||||
|
||||
if self.mcp_client.implementation_type == "custom_garth":
|
||||
try:
|
||||
cache_stats = self.mcp_client.get_cache_stats()
|
||||
print(f"\nCache Status: {cache_stats.get('total_entries', 0)} entries")
|
||||
for key in cache_stats.get('keys', []):
|
||||
print(f" - {key}")
|
||||
except Exception as e:
|
||||
logger.debug(f"Could not get cache stats: {e}")
|
||||
|
||||
def validate_config(config: Config) -> bool:
|
||||
"""Validate configuration"""
|
||||
issues = []
|
||||
|
||||
if not config.garth_token:
|
||||
issues.append("GARTH_TOKEN not set")
|
||||
|
||||
if issues:
|
||||
print("❌ Configuration issues:")
|
||||
for issue in issues:
|
||||
print(f" - {issue}")
|
||||
print("\nTo fix:")
|
||||
print("1. Run 'pip install garth' to install Garth module")
|
||||
print("2. Run authentication to get token")
|
||||
print("3. Update config.yaml with your token")
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
async def main():
|
||||
"""Main entry point"""
|
||||
print("Custom MCP Test App - Test Custom Garth Implementation")
|
||||
print("=" * 60)
|
||||
|
||||
try:
|
||||
# Setup config
|
||||
create_sample_config()
|
||||
config = load_config()
|
||||
|
||||
if not validate_config(config):
|
||||
return
|
||||
|
||||
# Create and run test app
|
||||
app = CustomMCPTestApp(config)
|
||||
|
||||
try:
|
||||
await app.initialize()
|
||||
results = await app.run_all_tests()
|
||||
|
||||
# Exit with appropriate code
|
||||
passed_tests = sum(1 for result in results.values() if result)
|
||||
total_tests = len(results)
|
||||
|
||||
if passed_tests == total_tests:
|
||||
print(f"\n🎉 All {total_tests} tests passed!")
|
||||
sys.exit(0)
|
||||
elif passed_tests > 0:
|
||||
print(f"\n⚠️ {passed_tests}/{total_tests} tests passed")
|
||||
sys.exit(1)
|
||||
else:
|
||||
print(f"\n❌ All {total_tests} tests failed")
|
||||
sys.exit(1)
|
||||
|
||||
finally:
|
||||
await app.cleanup()
|
||||
|
||||
except KeyboardInterrupt:
|
||||
print("\n👋 Test interrupted")
|
||||
except Exception as e:
|
||||
print(f"\n💥 Test error: {e}")
|
||||
logger.error(f"Main error: {e}", exc_info=True)
|
||||
sys.exit(1)
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
263
tests/test_mcp_app.py
Normal file
263
tests/test_mcp_app.py
Normal file
@@ -0,0 +1,263 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Simple MCP Test App - Test MCP connection and user profile loading
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import json
|
||||
import logging
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
# Import our modules
|
||||
from config import Config, load_config, create_sample_config
|
||||
from mcp_client import MCPClient
|
||||
from cache_manager import CacheManager
|
||||
|
||||
# Configure logging
|
||||
logging.basicConfig(
|
||||
level=logging.INFO,
|
||||
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
|
||||
)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class MCPTestApp:
|
||||
"""Simple test application for MCP functionality"""
|
||||
|
||||
def __init__(self, config: Config):
|
||||
self.config = config
|
||||
self.mcp_client = MCPClient(config)
|
||||
self.cache_manager = CacheManager(default_ttl=300)
|
||||
|
||||
async def initialize(self):
|
||||
"""Initialize MCP client"""
|
||||
logger.info("Initializing MCP test app...")
|
||||
await self.mcp_client.initialize()
|
||||
|
||||
async def cleanup(self):
|
||||
"""Cleanup resources"""
|
||||
await self.mcp_client.cleanup()
|
||||
|
||||
async def test_mcp_connection(self):
|
||||
"""Test basic MCP connection and list tools"""
|
||||
print("\n" + "="*60)
|
||||
print("MCP CONNECTION TEST")
|
||||
print("="*60)
|
||||
|
||||
if not self.mcp_client.is_available:
|
||||
print("❌ MCP server not available")
|
||||
return False
|
||||
|
||||
print("✅ MCP server connected")
|
||||
|
||||
# List available tools
|
||||
tools = await self.mcp_client.list_tools()
|
||||
print(f"📋 Found {len(tools)} tools:")
|
||||
|
||||
if tools:
|
||||
for i, tool in enumerate(tools, 1):
|
||||
print(f" {i}. {tool.name}")
|
||||
if hasattr(tool, 'description') and tool.description:
|
||||
print(f" {tool.description}")
|
||||
else:
|
||||
print(" No tools available")
|
||||
|
||||
return len(tools) > 0
|
||||
|
||||
async def test_user_profile(self):
|
||||
"""Test user profile loading and caching"""
|
||||
print("\n" + "="*60)
|
||||
print("USER PROFILE TEST")
|
||||
print("="*60)
|
||||
|
||||
# Check if user_profile tool is available
|
||||
if not await self.mcp_client.has_tool("user_profile"):
|
||||
print("❌ user_profile tool not available")
|
||||
return None
|
||||
|
||||
print("✅ user_profile tool found")
|
||||
|
||||
try:
|
||||
# Call user_profile tool
|
||||
print("📞 Calling user_profile tool...")
|
||||
profile_data = await self.mcp_client.call_tool("user_profile", {})
|
||||
|
||||
# Cache the profile
|
||||
self.cache_manager.set("user_profile", profile_data, ttl=3600)
|
||||
print("💾 User profile cached (TTL: 1 hour)")
|
||||
|
||||
# Pretty print the profile
|
||||
print("\n" + "-"*40)
|
||||
print("USER PROFILE DATA:")
|
||||
print("-"*40)
|
||||
print(json.dumps(profile_data, indent=2, default=str))
|
||||
print("-"*40)
|
||||
|
||||
return profile_data
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ Error getting user profile: {e}")
|
||||
logger.error(f"User profile error: {e}", exc_info=True)
|
||||
return None
|
||||
|
||||
async def test_cached_retrieval(self):
|
||||
"""Test retrieving cached user profile"""
|
||||
print("\n" + "="*60)
|
||||
print("CACHE RETRIEVAL TEST")
|
||||
print("="*60)
|
||||
|
||||
# Try to get cached profile
|
||||
cached_profile = self.cache_manager.get("user_profile")
|
||||
|
||||
if cached_profile:
|
||||
print("✅ User profile retrieved from cache")
|
||||
print(f"📊 Cache stats: {self.cache_manager.get_stats()}")
|
||||
return cached_profile
|
||||
else:
|
||||
print("❌ No cached user profile found")
|
||||
return None
|
||||
|
||||
async def test_activities_preview(self):
|
||||
"""Test getting recent activities if available"""
|
||||
print("\n" + "="*60)
|
||||
print("ACTIVITIES PREVIEW TEST")
|
||||
print("="*60)
|
||||
|
||||
if not await self.mcp_client.has_tool("get_activities"):
|
||||
print("❌ get_activities tool not available")
|
||||
return None
|
||||
|
||||
print("✅ get_activities tool found")
|
||||
|
||||
try:
|
||||
print("📞 Calling get_activities (limit=5)...")
|
||||
activities = await self.mcp_client.call_tool("get_activities", {"limit": 5})
|
||||
|
||||
if activities:
|
||||
print(f"📋 Retrieved {len(activities)} activities")
|
||||
|
||||
# Show basic info for each activity
|
||||
print("\nRecent Activities:")
|
||||
for i, activity in enumerate(activities[:3], 1): # Show first 3
|
||||
activity_type = activity.get('activityType', {}).get('typeKey', 'Unknown')
|
||||
start_time = activity.get('startTimeLocal', 'Unknown time')
|
||||
duration = activity.get('duration', 0)
|
||||
|
||||
print(f" {i}. {activity_type} - {start_time}")
|
||||
print(f" Duration: {duration // 60}m {duration % 60}s")
|
||||
|
||||
# Cache activities
|
||||
self.cache_manager.set("recent_activities", activities, ttl=900)
|
||||
print("💾 Activities cached (TTL: 15 minutes)")
|
||||
|
||||
return activities
|
||||
else:
|
||||
print("📋 No activities found")
|
||||
return []
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ Error getting activities: {e}")
|
||||
logger.error(f"Activities error: {e}", exc_info=True)
|
||||
return None
|
||||
|
||||
async def run_all_tests(self):
|
||||
"""Run all tests in sequence"""
|
||||
print("🚀 Starting MCP Test Suite")
|
||||
|
||||
results = {}
|
||||
|
||||
# Test 1: MCP Connection
|
||||
results['mcp_connection'] = await self.test_mcp_connection()
|
||||
|
||||
if not results['mcp_connection']:
|
||||
print("\n❌ MCP connection failed - skipping remaining tests")
|
||||
return results
|
||||
|
||||
# Test 2: User Profile
|
||||
results['user_profile'] = await self.test_user_profile()
|
||||
|
||||
# Test 3: Cache Retrieval
|
||||
results['cache_retrieval'] = await self.test_cached_retrieval()
|
||||
|
||||
# Test 4: Activities Preview (optional)
|
||||
results['activities'] = await self.test_activities_preview()
|
||||
|
||||
# Summary
|
||||
print("\n" + "="*60)
|
||||
print("TEST SUMMARY")
|
||||
print("="*60)
|
||||
|
||||
for test_name, result in results.items():
|
||||
status = "✅ PASS" if result else "❌ FAIL"
|
||||
print(f"{test_name.replace('_', ' ').title()}: {status}")
|
||||
|
||||
# Cache summary
|
||||
cache_stats = self.cache_manager.get_stats()
|
||||
print(f"\nCache Status: {cache_stats['total_entries']} entries")
|
||||
for key in cache_stats['keys']:
|
||||
print(f" - {key}")
|
||||
|
||||
return results
|
||||
|
||||
def validate_config(config: Config) -> bool:
|
||||
"""Validate configuration for MCP testing"""
|
||||
issues = []
|
||||
|
||||
if not config.garth_token:
|
||||
issues.append("GARTH_TOKEN not set")
|
||||
|
||||
if not config.garth_mcp_server_path:
|
||||
issues.append("garth_mcp_server_path not set")
|
||||
|
||||
if issues:
|
||||
print("❌ Configuration issues:")
|
||||
for issue in issues:
|
||||
print(f" - {issue}")
|
||||
print("\nTo fix:")
|
||||
print("1. Run 'uvx garth login' to get GARTH_TOKEN")
|
||||
print("2. Install garth-mcp-server: 'npm install -g garth-mcp-server'")
|
||||
print("3. Update config.yaml with your tokens")
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
async def main():
|
||||
"""Main entry point"""
|
||||
print("MCP Test App - Simple MCP and User Profile Test")
|
||||
print("=" * 50)
|
||||
|
||||
try:
|
||||
# Setup config
|
||||
create_sample_config()
|
||||
config = load_config()
|
||||
|
||||
if not validate_config(config):
|
||||
return
|
||||
|
||||
# Create and run test app
|
||||
app = MCPTestApp(config)
|
||||
|
||||
try:
|
||||
await app.initialize()
|
||||
results = await app.run_all_tests()
|
||||
|
||||
# Exit with appropriate code
|
||||
if all(results.values()):
|
||||
print("\n🎉 All tests passed!")
|
||||
sys.exit(0)
|
||||
else:
|
||||
print("\n⚠️ Some tests failed")
|
||||
sys.exit(1)
|
||||
|
||||
finally:
|
||||
await app.cleanup()
|
||||
|
||||
except KeyboardInterrupt:
|
||||
print("\n👋 Test interrupted")
|
||||
except Exception as e:
|
||||
print(f"\n💥 Test error: {e}")
|
||||
logger.error(f"Main error: {e}", exc_info=True)
|
||||
sys.exit(1)
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
20
tests/test_template_render.py
Normal file
20
tests/test_template_render.py
Normal file
@@ -0,0 +1,20 @@
|
||||
import asyncio
|
||||
from config import load_config
|
||||
from core_app import CyclingAnalyzerApp
|
||||
|
||||
async def main():
|
||||
config = load_config()
|
||||
app = CyclingAnalyzerApp(config, test_mode=True)
|
||||
await app.initialize()
|
||||
activity_data = app.cache_manager.get("last_cycling_details")
|
||||
print("=== ACTIVITY DATA KEYS ===")
|
||||
print(list(activity_data.keys()) if activity_data else "No activity data")
|
||||
print("\nSample data:", dict(list(activity_data.items())[:10]) if activity_data else "No data")
|
||||
|
||||
result = await app.analyze_workout("analyze_last_workout")
|
||||
print("=== TEST RESULT ===")
|
||||
print(result)
|
||||
await app.cleanup()
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
Reference in New Issue
Block a user