#!/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())