""" Tests for Enhanced Error Handling and User Guidance System Tests the comprehensive error handling system including error detection, suggestions, recovery guidance, and configuration validation. """ import pytest from typing import Set, List from components.charts.config.error_handling import ( ErrorSeverity, ErrorCategory, ConfigurationError, ErrorReport, ConfigurationErrorHandler, validate_configuration_strict, validate_strategy_name, get_indicator_suggestions, get_strategy_suggestions, check_configuration_health ) from components.charts.config.strategy_charts import ( StrategyChartConfig, SubplotConfig, ChartStyle, ChartLayout, SubplotType ) from components.charts.config.defaults import TradingStrategy class TestConfigurationError: """Test ConfigurationError class.""" def test_configuration_error_creation(self): """Test ConfigurationError creation with all fields.""" error = ConfigurationError( category=ErrorCategory.MISSING_INDICATOR, severity=ErrorSeverity.HIGH, message="Test error message", field_path="overlay_indicators[ema_99]", missing_item="ema_99", suggestions=["Use ema_12 instead", "Try different period"], alternatives=["ema_12", "ema_26"], recovery_steps=["Replace with ema_12", "Check available indicators"] ) assert error.category == ErrorCategory.MISSING_INDICATOR assert error.severity == ErrorSeverity.HIGH assert error.message == "Test error message" assert error.field_path == "overlay_indicators[ema_99]" assert error.missing_item == "ema_99" assert len(error.suggestions) == 2 assert len(error.alternatives) == 2 assert len(error.recovery_steps) == 2 def test_configuration_error_string_representation(self): """Test string representation with emojis and formatting.""" error = ConfigurationError( category=ErrorCategory.MISSING_INDICATOR, severity=ErrorSeverity.CRITICAL, message="Indicator 'ema_99' not found", suggestions=["Use ema_12"], alternatives=["ema_12", "ema_26"], recovery_steps=["Replace with available indicator"] ) error_str = str(error) assert "🚨" in error_str # Critical severity emoji assert "Indicator 'ema_99' not found" in error_str assert "💡 Suggestions:" in error_str assert "🔄 Alternatives:" in error_str assert "🔧 Recovery steps:" in error_str class TestErrorReport: """Test ErrorReport class.""" def test_error_report_creation(self): """Test ErrorReport creation and basic functionality.""" report = ErrorReport(is_usable=True) assert report.is_usable is True assert len(report.errors) == 0 assert len(report.missing_strategies) == 0 assert len(report.missing_indicators) == 0 assert report.report_time is not None def test_add_error_updates_usability(self): """Test that adding critical/high errors updates usability.""" report = ErrorReport(is_usable=True) # Add medium error - should remain usable medium_error = ConfigurationError( category=ErrorCategory.INVALID_PARAMETER, severity=ErrorSeverity.MEDIUM, message="Medium error" ) report.add_error(medium_error) assert report.is_usable is True # Add critical error - should become unusable critical_error = ConfigurationError( category=ErrorCategory.MISSING_STRATEGY, severity=ErrorSeverity.CRITICAL, message="Critical error", missing_item="test_strategy" ) report.add_error(critical_error) assert report.is_usable is False assert "test_strategy" in report.missing_strategies def test_add_missing_indicator_tracking(self): """Test tracking of missing indicators.""" report = ErrorReport(is_usable=True) error = ConfigurationError( category=ErrorCategory.MISSING_INDICATOR, severity=ErrorSeverity.HIGH, message="Indicator missing", missing_item="ema_99" ) report.add_error(error) assert "ema_99" in report.missing_indicators assert report.is_usable is False # High severity def test_get_critical_and_high_priority_errors(self): """Test filtering errors by severity.""" report = ErrorReport(is_usable=True) # Add different severity errors report.add_error(ConfigurationError( category=ErrorCategory.MISSING_INDICATOR, severity=ErrorSeverity.CRITICAL, message="Critical error" )) report.add_error(ConfigurationError( category=ErrorCategory.MISSING_INDICATOR, severity=ErrorSeverity.HIGH, message="High error" )) report.add_error(ConfigurationError( category=ErrorCategory.INVALID_PARAMETER, severity=ErrorSeverity.MEDIUM, message="Medium error" )) critical_errors = report.get_critical_errors() high_errors = report.get_high_priority_errors() assert len(critical_errors) == 1 assert len(high_errors) == 1 assert critical_errors[0].message == "Critical error" assert high_errors[0].message == "High error" def test_summary_generation(self): """Test error report summary.""" # Empty report empty_report = ErrorReport(is_usable=True) assert "✅ No configuration errors found" in empty_report.summary() # Report with errors report = ErrorReport(is_usable=False) report.add_error(ConfigurationError( category=ErrorCategory.MISSING_INDICATOR, severity=ErrorSeverity.CRITICAL, message="Critical error" )) report.add_error(ConfigurationError( category=ErrorCategory.INVALID_PARAMETER, severity=ErrorSeverity.MEDIUM, message="Medium error" )) summary = report.summary() assert "❌ Cannot proceed" in summary assert "2 errors" in summary assert "1 critical" in summary class TestConfigurationErrorHandler: """Test ConfigurationErrorHandler class.""" def test_handler_initialization(self): """Test error handler initialization.""" handler = ConfigurationErrorHandler() assert len(handler.indicator_names) > 0 assert len(handler.strategy_names) > 0 assert "ema_12" in handler.indicator_names assert "ema_crossover" in handler.strategy_names def test_validate_existing_strategy(self): """Test validation of existing strategy.""" handler = ConfigurationErrorHandler() # Test existing strategy error = handler.validate_strategy_exists("ema_crossover") assert error is None def test_validate_missing_strategy(self): """Test validation of missing strategy with suggestions.""" handler = ConfigurationErrorHandler() # Test missing strategy error = handler.validate_strategy_exists("non_existent_strategy") assert error is not None assert error.category == ErrorCategory.MISSING_STRATEGY assert error.severity == ErrorSeverity.CRITICAL assert "non_existent_strategy" in error.message assert len(error.recovery_steps) > 0 def test_validate_similar_strategy_name(self): """Test suggestions for similar strategy names.""" handler = ConfigurationErrorHandler() # Test typo in strategy name error = handler.validate_strategy_exists("ema_cross") # Similar to "ema_crossover" assert error is not None assert len(error.alternatives) > 0 assert "ema_crossover" in error.alternatives or any("ema" in alt for alt in error.alternatives) def test_validate_existing_indicator(self): """Test validation of existing indicator.""" handler = ConfigurationErrorHandler() # Test existing indicator error = handler.validate_indicator_exists("ema_12") assert error is None def test_validate_missing_indicator(self): """Test validation of missing indicator with suggestions.""" handler = ConfigurationErrorHandler() # Test missing indicator error = handler.validate_indicator_exists("ema_999") assert error is not None assert error.category == ErrorCategory.MISSING_INDICATOR assert error.severity in [ErrorSeverity.CRITICAL, ErrorSeverity.HIGH] assert "ema_999" in error.message assert len(error.recovery_steps) > 0 def test_indicator_category_suggestions(self): """Test category-based suggestions for missing indicators.""" handler = ConfigurationErrorHandler() # Test SMA suggestion sma_error = handler.validate_indicator_exists("sma_999") assert sma_error is not None # Check for SMA-related suggestions in any form assert any("sma" in suggestion.lower() or "trend" in suggestion.lower() for suggestion in sma_error.suggestions) # Test RSI suggestion rsi_error = handler.validate_indicator_exists("rsi_999") assert rsi_error is not None # Check that RSI alternatives contain actual RSI indicators assert any("rsi_" in alternative for alternative in rsi_error.alternatives) # Test MACD suggestion macd_error = handler.validate_indicator_exists("macd_999") assert macd_error is not None # Check that MACD alternatives contain actual MACD indicators assert any("macd_" in alternative for alternative in macd_error.alternatives) def test_validate_strategy_configuration_empty(self): """Test validation of empty configuration.""" handler = ConfigurationErrorHandler() # Empty configuration config = StrategyChartConfig( strategy_name="Empty Strategy", strategy_type=TradingStrategy.DAY_TRADING, description="Empty strategy", timeframes=["1h"], overlay_indicators=[], subplot_configs=[] ) report = handler.validate_strategy_configuration(config) assert not report.is_usable assert len(report.errors) > 0 assert any(error.category == ErrorCategory.CONFIGURATION_CORRUPT for error in report.errors) def test_validate_strategy_configuration_with_missing_indicators(self): """Test validation with missing indicators.""" handler = ConfigurationErrorHandler() config = StrategyChartConfig( strategy_name="Test Strategy", strategy_type=TradingStrategy.DAY_TRADING, description="Test strategy", timeframes=["1h"], overlay_indicators=["ema_999", "sma_888"], # Missing indicators subplot_configs=[ SubplotConfig( subplot_type=SubplotType.RSI, indicators=["rsi_777"] # Missing indicator ) ] ) report = handler.validate_strategy_configuration(config) assert not report.is_usable assert len(report.missing_indicators) == 3 assert "ema_999" in report.missing_indicators assert "sma_888" in report.missing_indicators assert "rsi_777" in report.missing_indicators def test_strategy_consistency_validation(self): """Test strategy type consistency validation.""" handler = ConfigurationErrorHandler() # Scalping strategy with wrong timeframes config = StrategyChartConfig( strategy_name="Scalping Strategy", strategy_type=TradingStrategy.SCALPING, description="Scalping strategy", timeframes=["1d", "1w"], # Wrong for scalping overlay_indicators=["ema_12"] ) report = handler.validate_strategy_configuration(config) # Should have consistency warning consistency_errors = [e for e in report.errors if e.category == ErrorCategory.INVALID_PARAMETER] assert len(consistency_errors) > 0 def test_suggest_alternatives_for_missing_indicators(self): """Test alternative suggestions for missing indicators.""" handler = ConfigurationErrorHandler() missing_indicators = {"ema_999", "rsi_777", "unknown_indicator"} suggestions = handler.suggest_alternatives_for_missing_indicators(missing_indicators) assert "ema_999" in suggestions assert "rsi_777" in suggestions # Should have EMA alternatives for ema_999 assert any("ema_" in alt for alt in suggestions.get("ema_999", [])) # Should have RSI alternatives for rsi_777 assert any("rsi_" in alt for alt in suggestions.get("rsi_777", [])) class TestUtilityFunctions: """Test utility functions.""" def test_validate_configuration_strict(self): """Test strict configuration validation.""" # Valid configuration valid_config = StrategyChartConfig( strategy_name="Valid Strategy", strategy_type=TradingStrategy.DAY_TRADING, description="Valid strategy", timeframes=["1h"], overlay_indicators=["ema_12", "sma_20"] ) report = validate_configuration_strict(valid_config) assert report.is_usable # Invalid configuration invalid_config = StrategyChartConfig( strategy_name="Invalid Strategy", strategy_type=TradingStrategy.DAY_TRADING, description="Invalid strategy", timeframes=["1h"], overlay_indicators=["ema_999"] # Missing indicator ) report = validate_configuration_strict(invalid_config) assert not report.is_usable assert len(report.missing_indicators) > 0 def test_validate_strategy_name_function(self): """Test strategy name validation function.""" # Valid strategy error = validate_strategy_name("ema_crossover") assert error is None # Invalid strategy error = validate_strategy_name("non_existent_strategy") assert error is not None assert error.category == ErrorCategory.MISSING_STRATEGY def test_get_indicator_suggestions(self): """Test indicator suggestions.""" # Test exact match suggestions suggestions = get_indicator_suggestions("ema") assert len(suggestions) > 0 assert any("ema_" in suggestion for suggestion in suggestions) # Test partial match suggestions = get_indicator_suggestions("ema_1") assert len(suggestions) > 0 # Test no match suggestions = get_indicator_suggestions("xyz_999") # Should return some suggestions even for no match assert isinstance(suggestions, list) def test_get_strategy_suggestions(self): """Test strategy suggestions.""" # Test exact match suggestions suggestions = get_strategy_suggestions("ema") assert len(suggestions) > 0 # Test partial match suggestions = get_strategy_suggestions("cross") assert len(suggestions) > 0 # Test no match suggestions = get_strategy_suggestions("xyz_999") assert isinstance(suggestions, list) def test_check_configuration_health(self): """Test configuration health check.""" # Healthy configuration healthy_config = StrategyChartConfig( strategy_name="Healthy Strategy", strategy_type=TradingStrategy.DAY_TRADING, description="Healthy strategy", timeframes=["1h"], overlay_indicators=["ema_12", "sma_20"], subplot_configs=[ SubplotConfig( subplot_type=SubplotType.RSI, indicators=["rsi_14"] ) ] ) health = check_configuration_health(healthy_config) assert "is_healthy" in health assert "error_report" in health assert "total_indicators" in health assert "has_trend_indicators" in health assert "has_momentum_indicators" in health assert "recommendations" in health assert health["total_indicators"] == 3 assert health["has_trend_indicators"] is True assert health["has_momentum_indicators"] is True # Unhealthy configuration unhealthy_config = StrategyChartConfig( strategy_name="Unhealthy Strategy", strategy_type=TradingStrategy.DAY_TRADING, description="Unhealthy strategy", timeframes=["1h"], overlay_indicators=["ema_999"] # Missing indicator ) health = check_configuration_health(unhealthy_config) assert health["is_healthy"] is False assert health["missing_indicators"] > 0 assert len(health["recommendations"]) > 0 class TestErrorSeverityAndCategories: """Test error severity and category enums.""" def test_error_severity_values(self): """Test ErrorSeverity enum values.""" assert ErrorSeverity.CRITICAL == "critical" assert ErrorSeverity.HIGH == "high" assert ErrorSeverity.MEDIUM == "medium" assert ErrorSeverity.LOW == "low" def test_error_category_values(self): """Test ErrorCategory enum values.""" assert ErrorCategory.MISSING_STRATEGY == "missing_strategy" assert ErrorCategory.MISSING_INDICATOR == "missing_indicator" assert ErrorCategory.INVALID_PARAMETER == "invalid_parameter" assert ErrorCategory.DEPENDENCY_MISSING == "dependency_missing" assert ErrorCategory.CONFIGURATION_CORRUPT == "configuration_corrupt" class TestRecoveryGeneration: """Test recovery configuration generation.""" def test_recovery_configuration_generation(self): """Test generating recovery configurations.""" handler = ConfigurationErrorHandler() # Configuration with missing indicators config = StrategyChartConfig( strategy_name="Broken Strategy", strategy_type=TradingStrategy.DAY_TRADING, description="Strategy with missing indicators", timeframes=["1h"], overlay_indicators=["ema_999", "ema_12"], # One missing, one valid subplot_configs=[ SubplotConfig( subplot_type=SubplotType.RSI, indicators=["rsi_777"] # Missing ) ] ) # Validate to get error report error_report = handler.validate_strategy_configuration(config) # Generate recovery recovery_config, recovery_notes = handler.generate_recovery_configuration(config, error_report) assert recovery_config is not None assert len(recovery_notes) > 0 assert "(Recovery)" in recovery_config.strategy_name # Should have valid indicators only for indicator in recovery_config.overlay_indicators: assert indicator in handler.indicator_names for subplot in recovery_config.subplot_configs: for indicator in subplot.indicators: assert indicator in handler.indicator_names class TestIntegrationWithExistingSystems: """Test integration with existing validation and configuration systems.""" def test_integration_with_strategy_validation(self): """Test integration with existing strategy validation.""" from components.charts.config import create_ema_crossover_strategy # Get a known good strategy strategy = create_ema_crossover_strategy() config = strategy.config # Test with error handler report = validate_configuration_strict(config) # Should be usable (might have warnings about missing indicators in test environment) assert isinstance(report, ErrorReport) assert hasattr(report, 'is_usable') assert hasattr(report, 'errors') def test_error_handling_with_custom_configuration(self): """Test error handling with custom configurations.""" from components.charts.config import create_custom_strategy_config # Try to create config with missing indicators config, errors = create_custom_strategy_config( strategy_name="Test Strategy", strategy_type=TradingStrategy.DAY_TRADING, description="Test strategy", timeframes=["1h"], overlay_indicators=["ema_999"], # Missing indicator subplot_configs=[{ "subplot_type": "rsi", "height_ratio": 0.2, "indicators": ["rsi_777"] # Missing indicator }] ) if config: # If config was created despite missing indicators report = validate_configuration_strict(config) assert not report.is_usable assert len(report.missing_indicators) > 0 if __name__ == "__main__": pytest.main([__file__])