#!/usr/bin/env python3 """ Application startup and basic functionality validation script. Tests both v1.0 CLI and v2.0 conversational interfaces. """ import asyncio import json import logging import os import sys import tempfile import traceback from pathlib import Path from typing import Dict, Any, Optional, List, Tuple # Add the current directory to Python path for imports sys.path.insert(0, str(Path(__file__).parent)) # Configure logging for validation logging.basicConfig( level=logging.INFO, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s" ) logger = logging.getLogger("StartupValidation") class ValidationResult: """Container for validation test results""" def __init__(self, test_name: str): self.test_name = test_name self.success = False self.message = "" self.details: Dict[str, Any] = {} self.error: Optional[Exception] = None def set_success(self, message: str, details: Optional[Dict[str, Any]] = None): self.success = True self.message = message self.details = details or {} def set_failure(self, message: str, error: Optional[Exception] = None, details: Optional[Dict[str, Any]] = None): self.success = False self.message = message self.error = error self.details = details or {} def __str__(self) -> str: status = "✅ PASS" if self.success else "❌ FAIL" return f"{status} {self.test_name}: {self.message}" class StartupValidator: """Validates application startup and basic functionality""" def __init__(self): self.results: List[ValidationResult] = [] self.temp_config_path: Optional[Path] = None def create_test_config(self) -> Path: """Create a temporary test configuration file""" config_content = { 'obsidian': { 'vault_path': '/tmp/test_vault', 'rest_api': { 'url': 'https://localhost:27123', 'api_key': 'test-api-key', 'verify_ssl': False } }, 'claude': { 'api_key': '${ANTHROPIC_API_KEY}', 'model': 'claude-3-5-sonnet-20241022', 'max_tokens': 4096, 'temperature': 0.7 }, 'journal': { 'daily_notes_folder': 'Daily', 'date_format': 'YYYY-MM-DD', 'file_extension': '.md' }, 'output': { 'experiences_folder': 'Knowledge/Experiences', 'lessons_folder': 'Knowledge/Lessons', 'tasks_folder': 'Tasks/Daily', 'problems_folder': 'Knowledge/Problems', 'achievements_folder': 'Knowledge/Achievements', 'improvements_folder': 'Knowledge/Improvements' }, 'logging': { 'level': 'INFO', 'file': 'logs/journal_organizer.log' } } # Create temporary config file temp_file = tempfile.NamedTemporaryFile(mode='w', suffix='.json', delete=False) json.dump(config_content, temp_file, indent=2) temp_file.close() self.temp_config_path = Path(temp_file.name) return self.temp_config_path def cleanup_test_config(self): """Clean up temporary test configuration""" if self.temp_config_path and self.temp_config_path.exists(): self.temp_config_path.unlink() async def test_import_main_modules(self) -> ValidationResult: """Test importing main application modules""" result = ValidationResult("Import Main Modules") try: # Test importing core modules modules_tested = [] # Test core modules try: import main modules_tested.append("main") except ImportError as e: logger.warning(f"Could not import main: {e}") try: import chat_main modules_tested.append("chat_main") except ImportError as e: logger.warning(f"Could not import chat_main: {e}") try: import agent_core modules_tested.append("agent_core") except ImportError as e: logger.warning(f"Could not import agent_core: {e}") try: import config modules_tested.append("config") except ImportError as e: logger.warning(f"Could not import config: {e}") # Test command modules try: from commands import organize_command modules_tested.append("commands.organize_command") except ImportError as e: logger.warning(f"Could not import commands.organize_command: {e}") # Test skill modules try: from skills import obsidian_skill modules_tested.append("skills.obsidian_skill") except ImportError as e: logger.warning(f"Could not import skills.obsidian_skill: {e}") try: from skills import claude_skill modules_tested.append("skills.claude_skill") except ImportError as e: logger.warning(f"Could not import skills.claude_skill: {e}") # Test conversation modules try: from conversation import conversational_agent modules_tested.append("conversation.conversational_agent") except ImportError as e: logger.warning(f"Could not import conversation.conversational_agent: {e}") try: from conversation import conversation_state modules_tested.append("conversation.conversation_state") except ImportError as e: logger.warning(f"Could not import conversation.conversation_state: {e}") if len(modules_tested) >= 4: # At least core modules should work result.set_success(f"Successfully imported {len(modules_tested)} modules", { "modules_tested": modules_tested, "total_attempted": 9 }) else: result.set_failure(f"Only imported {len(modules_tested)} out of 9 modules", details={ "modules_tested": modules_tested }) except Exception as e: result.set_failure(f"Failed to import modules: {str(e)}", e) return result async def test_agent_initialization(self) -> ValidationResult: """Test basic agent initialization""" result = ValidationResult("Agent Initialization") try: # Try to import and test agent initialization try: from main import JournalOrganizerAgent except ImportError: # If relative import fails, skip this test result.set_failure("Could not import JournalOrganizerAgent - likely due to package structure") return result # Create test config config_path = self.create_test_config() # Initialize agent agent = JournalOrganizerAgent(str(config_path)) # Test basic properties assert hasattr(agent, 'config'), "Agent should have config attribute" assert hasattr(agent, 'agent'), "Agent should have agent attribute" assert hasattr(agent, 'logger'), "Agent should have logger attribute" # Test command listing commands = agent.list_commands() assert isinstance(commands, list), "Commands should be a list" assert len(commands) > 0, "Should have at least one command" result.set_success("Agent initialized successfully", { "available_commands": commands, "config_loaded": bool(agent.config) }) except Exception as e: result.set_failure(f"Agent initialization failed: {str(e)}", e) return result async def test_configuration_loading(self) -> ValidationResult: """Test configuration loading with various scenarios""" result = ValidationResult("Configuration Loading") try: from main import JournalOrganizerAgent test_results = {} # Test 1: Load from specific config file config_path = self.create_test_config() agent1 = JournalOrganizerAgent(str(config_path)) test_results["specific_config"] = bool(agent1.config) # Test 2: Load with no config file (should use defaults) agent2 = JournalOrganizerAgent(None) test_results["default_config"] = isinstance(agent2.config, dict) # Test 3: Load with non-existent config file (should use defaults) agent3 = JournalOrganizerAgent("/non/existent/config.yaml") test_results["fallback_config"] = isinstance(agent3.config, dict) result.set_success("Configuration loading works correctly", test_results) except Exception as e: result.set_failure(f"Configuration loading failed: {str(e)}", e) return result async def test_command_registration(self) -> ValidationResult: """Test command registration and basic info retrieval""" result = ValidationResult("Command Registration") try: from main import JournalOrganizerAgent config_path = self.create_test_config() agent = JournalOrganizerAgent(str(config_path)) # Test command listing commands = agent.list_commands() assert "organize" in commands, "Should have 'organize' command" # Test command info retrieval organize_info = agent.get_command_info("organize") assert isinstance(organize_info, dict), "Command info should be a dict" assert "name" in organize_info, "Command info should have name" # Test all commands info all_info = agent.get_all_commands_info() assert isinstance(all_info, dict), "All commands info should be a dict" result.set_success("Command registration working correctly", { "registered_commands": commands, "organize_command_info": bool(organize_info), "all_commands_info": bool(all_info) }) except Exception as e: result.set_failure(f"Command registration failed: {str(e)}", e) return result async def test_conversational_agent_init(self) -> ValidationResult: """Test conversational agent initialization""" result = ValidationResult("Conversational Agent Initialization") try: from main import JournalOrganizerAgent from conversation import ConversationalAgent config_path = self.create_test_config() journal_agent = JournalOrganizerAgent(str(config_path)) # Initialize conversational agent conv_agent = ConversationalAgent(journal_agent.agent, journal_agent.config) # Test basic properties assert hasattr(conv_agent, 'agent'), "Should have agent attribute" assert hasattr(conv_agent, 'config'), "Should have config attribute" # Test initialization welcome_msg = await conv_agent.initialize() assert isinstance(welcome_msg, str), "Welcome message should be a string" assert len(welcome_msg) > 0, "Welcome message should not be empty" result.set_success("Conversational agent initialized successfully", { "welcome_message_length": len(welcome_msg), "has_required_attributes": True }) except Exception as e: result.set_failure(f"Conversational agent initialization failed: {str(e)}", e) return result async def test_dependency_management(self) -> ValidationResult: """Test dependency management and graceful degradation""" result = ValidationResult("Dependency Management") try: from dependency_manager import get_dependency_manager dep_manager = get_dependency_manager() # Test dependency status status_report = dep_manager.get_dependency_status_report() assert isinstance(status_report, str), "Status report should be a string" # Test missing dependencies check missing_deps = dep_manager.get_missing_dependencies() assert isinstance(missing_deps, list), "Missing deps should be a list" # Test module retrieval (should work even if module is missing) yaml_module = dep_manager.get_module('yaml') # yaml_module could be None if not installed, which is fine result.set_success("Dependency management working correctly", { "status_report_generated": bool(status_report), "missing_dependencies_count": len(missing_deps), "yaml_module_available": yaml_module is not None }) except Exception as e: result.set_failure(f"Dependency management failed: {str(e)}", e) return result async def test_error_handling_framework(self) -> ValidationResult: """Test error handling framework""" result = ValidationResult("Error Handling Framework") try: from error_handling import ( JournalOrganizerError, ConfigurationError, APIError, ValidationError, ErrorHandler ) # Test custom exceptions test_exceptions = [ JournalOrganizerError("test"), ConfigurationError("test config error"), APIError("test api error"), ValidationError("test validation error") ] for exc in test_exceptions: assert isinstance(exc, Exception), f"{type(exc).__name__} should be an Exception" assert str(exc), f"{type(exc).__name__} should have string representation" # Test ErrorHandler import logging logger = logging.getLogger("test") error_handler = ErrorHandler(logger) assert hasattr(error_handler, 'handle_api_error'), "Should have handle_api_error method" assert hasattr(error_handler, 'handle_validation_error'), "Should have handle_validation_error method" result.set_success("Error handling framework working correctly", { "custom_exceptions_count": len(test_exceptions), "error_handler_methods": ["handle_api_error", "handle_validation_error"] }) except Exception as e: result.set_failure(f"Error handling framework test failed: {str(e)}", e) return result async def run_all_tests(self) -> List[ValidationResult]: """Run all validation tests""" logger.info("🚀 Starting application startup validation...") test_methods = [ self.test_import_main_modules, self.test_agent_initialization, self.test_configuration_loading, self.test_command_registration, self.test_conversational_agent_init, self.test_dependency_management, self.test_error_handling_framework ] for test_method in test_methods: try: logger.info(f"Running {test_method.__name__}...") result = await test_method() self.results.append(result) logger.info(str(result)) except Exception as e: error_result = ValidationResult(test_method.__name__) error_result.set_failure(f"Test execution failed: {str(e)}", e) self.results.append(error_result) logger.error(str(error_result)) # Cleanup self.cleanup_test_config() return self.results def generate_report(self) -> str: """Generate a comprehensive validation report""" total_tests = len(self.results) passed_tests = sum(1 for r in self.results if r.success) failed_tests = total_tests - passed_tests report = f""" {'='*60} APPLICATION STARTUP VALIDATION REPORT {'='*60} Summary: Total Tests: {total_tests} Passed: {passed_tests} Failed: {failed_tests} Success Rate: {(passed_tests/total_tests*100):.1f}% Test Results: """ for result in self.results: report += f"\n{str(result)}" if result.details: for key, value in result.details.items(): report += f"\n - {key}: {value}" if not result.success and result.error: report += f"\n - Error: {str(result.error)}" report += f"\n\n{'='*60}\n" return report async def main(): """Main validation function""" validator = StartupValidator() try: results = await validator.run_all_tests() report = validator.generate_report() print(report) # Return appropriate exit code failed_count = sum(1 for r in results if not r.success) return 0 if failed_count == 0 else 1 except Exception as e: logger.error(f"Validation failed with unexpected error: {str(e)}") traceback.print_exc() return 1 if __name__ == "__main__": exit_code = asyncio.run(main()) sys.exit(exit_code)