TCPDashboard/tests/test_error_handling.py
Vasily.onl d71cb763bc 3.4 - 3.0 Strategy Configuration System
Implement comprehensive chart configuration and validation system

- Introduced a modular chart configuration system in `components/charts/config/` to manage indicator definitions, default configurations, and strategy-specific setups.
- Added new modules for error handling and validation, enhancing user guidance and error reporting capabilities.
- Implemented detailed schema validation for indicators and strategies, ensuring robust configuration management.
- Created example strategies and default configurations to facilitate user onboarding and usage.
- Enhanced documentation to provide clear guidelines on the configuration system, validation rules, and usage examples.
- Added unit tests for all new components to ensure functionality and reliability across the configuration system.
2025-06-03 14:33:25 +08:00

570 lines
22 KiB
Python

"""
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__])