""" Tests for Configuration Validation and Error Handling System Tests the comprehensive validation system including validation rules, error reporting, warnings, and detailed diagnostics. """ import pytest from typing import Set from datetime import datetime from components.charts.config.validation import ( ValidationLevel, ValidationRule, ValidationIssue, ValidationReport, ConfigurationValidator, validate_configuration, get_validation_rules_info ) from components.charts.config.strategy_charts import ( StrategyChartConfig, SubplotConfig, ChartStyle, ChartLayout, SubplotType ) from components.charts.config.defaults import TradingStrategy class TestValidationComponents: """Test validation component classes.""" def test_validation_level_enum(self): """Test ValidationLevel enum values.""" levels = [level.value for level in ValidationLevel] expected_levels = ["error", "warning", "info", "debug"] for expected in expected_levels: assert expected in levels def test_validation_rule_enum(self): """Test ValidationRule enum values.""" rules = [rule.value for rule in ValidationRule] expected_rules = [ "required_fields", "height_ratios", "indicator_existence", "timeframe_format", "chart_style", "subplot_config", "strategy_consistency", "performance_impact", "indicator_conflicts", "resource_usage" ] for expected in expected_rules: assert expected in rules def test_validation_issue_creation(self): """Test ValidationIssue creation and string representation.""" issue = ValidationIssue( level=ValidationLevel.ERROR, rule=ValidationRule.REQUIRED_FIELDS, message="Test error message", field_path="test.field", suggestion="Test suggestion" ) assert issue.level == ValidationLevel.ERROR assert issue.rule == ValidationRule.REQUIRED_FIELDS assert issue.message == "Test error message" assert issue.field_path == "test.field" assert issue.suggestion == "Test suggestion" # Test string representation issue_str = str(issue) assert "[ERROR]" in issue_str assert "Test error message" in issue_str assert "test.field" in issue_str assert "Test suggestion" in issue_str def test_validation_report_creation(self): """Test ValidationReport creation and methods.""" report = ValidationReport(is_valid=True) assert report.is_valid is True assert len(report.errors) == 0 assert len(report.warnings) == 0 assert len(report.info) == 0 assert len(report.debug) == 0 # Test adding issues error_issue = ValidationIssue( level=ValidationLevel.ERROR, rule=ValidationRule.REQUIRED_FIELDS, message="Error message" ) warning_issue = ValidationIssue( level=ValidationLevel.WARNING, rule=ValidationRule.HEIGHT_RATIOS, message="Warning message" ) report.add_issue(error_issue) report.add_issue(warning_issue) assert not report.is_valid # Should be False after adding error assert len(report.errors) == 1 assert len(report.warnings) == 1 assert report.has_errors() assert report.has_warnings() # Test get_all_issues all_issues = report.get_all_issues() assert len(all_issues) == 2 # Test get_issues_by_rule field_issues = report.get_issues_by_rule(ValidationRule.REQUIRED_FIELDS) assert len(field_issues) == 1 assert field_issues[0] == error_issue # Test summary summary = report.summary() assert "1 errors" in summary assert "1 warnings" in summary class TestConfigurationValidator: """Test ConfigurationValidator class.""" def create_valid_config(self) -> StrategyChartConfig: """Create a valid test configuration.""" return StrategyChartConfig( strategy_name="Valid Test Strategy", strategy_type=TradingStrategy.DAY_TRADING, description="Valid strategy for testing", timeframes=["5m", "15m", "1h"], main_chart_height=0.7, overlay_indicators=["sma_20"], # Using simple indicators subplot_configs=[ SubplotConfig( subplot_type=SubplotType.RSI, height_ratio=0.2, indicators=[], # Empty to avoid indicator existence issues title="RSI" ) ] ) def test_validator_initialization(self): """Test validator initialization.""" # Test with all rules validator = ConfigurationValidator() assert len(validator.enabled_rules) == len(ValidationRule) # Test with specific rules specific_rules = {ValidationRule.REQUIRED_FIELDS, ValidationRule.HEIGHT_RATIOS} validator = ConfigurationValidator(enabled_rules=specific_rules) assert validator.enabled_rules == specific_rules def test_validate_strategy_config_valid(self): """Test validation of a valid configuration.""" config = self.create_valid_config() validator = ConfigurationValidator() report = validator.validate_strategy_config(config) # Should have some validation applied assert isinstance(report, ValidationReport) assert report.validation_time is not None assert len(report.rules_applied) > 0 def test_required_fields_validation(self): """Test required fields validation.""" config = self.create_valid_config() validator = ConfigurationValidator(enabled_rules={ValidationRule.REQUIRED_FIELDS}) # Test missing strategy name config.strategy_name = "" report = validator.validate_strategy_config(config) assert not report.is_valid assert len(report.errors) > 0 assert any("Strategy name is required" in str(error) for error in report.errors) # Test short strategy name (should be warning) config.strategy_name = "AB" report = validator.validate_strategy_config(config) assert len(report.warnings) > 0 assert any("very short" in str(warning) for warning in report.warnings) # Test missing timeframes config.strategy_name = "Valid Name" config.timeframes = [] report = validator.validate_strategy_config(config) assert not report.is_valid assert any("timeframe must be specified" in str(error) for error in report.errors) def test_height_ratios_validation(self): """Test height ratios validation.""" config = self.create_valid_config() validator = ConfigurationValidator(enabled_rules={ValidationRule.HEIGHT_RATIOS}) # Test invalid main chart height config.main_chart_height = 1.5 # Invalid: > 1.0 report = validator.validate_strategy_config(config) assert not report.is_valid assert any("Main chart height" in str(error) for error in report.errors) # Test total height exceeding 1.0 config.main_chart_height = 0.8 config.subplot_configs[0].height_ratio = 0.3 # Total = 1.1 report = validator.validate_strategy_config(config) assert not report.is_valid assert any("exceeds 1.0" in str(error) for error in report.errors) # Test very small main chart height (should be warning) config.main_chart_height = 0.1 config.subplot_configs[0].height_ratio = 0.2 report = validator.validate_strategy_config(config) assert len(report.warnings) > 0 assert any("very small" in str(warning) for warning in report.warnings) def test_timeframe_format_validation(self): """Test timeframe format validation.""" config = self.create_valid_config() validator = ConfigurationValidator(enabled_rules={ValidationRule.TIMEFRAME_FORMAT}) # Test invalid timeframe format config.timeframes = ["invalid", "1h", "5m"] report = validator.validate_strategy_config(config) assert not report.is_valid assert any("Invalid timeframe format" in str(error) for error in report.errors) # Test valid but uncommon timeframe (should be warning) config.timeframes = ["7m", "1h"] # 7m is valid format but uncommon report = validator.validate_strategy_config(config) assert len(report.warnings) > 0 assert any("not in common list" in str(warning) for warning in report.warnings) def test_chart_style_validation(self): """Test chart style validation.""" config = self.create_valid_config() validator = ConfigurationValidator(enabled_rules={ValidationRule.CHART_STYLE}) # Test invalid color format config.chart_style.background_color = "invalid_color" report = validator.validate_strategy_config(config) assert not report.is_valid assert any("Invalid color format" in str(error) for error in report.errors) # Test extreme font size (should be warning or error) config.chart_style.background_color = "#ffffff" # Fix color config.chart_style.font_size = 2 # Too small report = validator.validate_strategy_config(config) assert len(report.errors) > 0 or len(report.warnings) > 0 # Test unsupported theme (should be warning) config.chart_style.font_size = 12 # Fix font size config.chart_style.theme = "unsupported_theme" report = validator.validate_strategy_config(config) assert len(report.warnings) > 0 assert any("may not be supported" in str(warning) for warning in report.warnings) def test_subplot_config_validation(self): """Test subplot configuration validation.""" config = self.create_valid_config() validator = ConfigurationValidator(enabled_rules={ValidationRule.SUBPLOT_CONFIG}) # Test duplicate subplot types config.subplot_configs.append(SubplotConfig( subplot_type=SubplotType.RSI, # Duplicate height_ratio=0.1, indicators=[], title="RSI 2" )) report = validator.validate_strategy_config(config) assert len(report.warnings) > 0 assert any("Duplicate subplot type" in str(warning) for warning in report.warnings) def test_strategy_consistency_validation(self): """Test strategy consistency validation.""" config = self.create_valid_config() validator = ConfigurationValidator(enabled_rules={ValidationRule.STRATEGY_CONSISTENCY}) # Test mismatched timeframes for scalping strategy config.strategy_type = TradingStrategy.SCALPING config.timeframes = ["4h", "1d"] # Not optimal for scalping report = validator.validate_strategy_config(config) assert len(report.info) > 0 assert any("may not be optimal" in str(info) for info in report.info) def test_performance_impact_validation(self): """Test performance impact validation.""" config = self.create_valid_config() validator = ConfigurationValidator(enabled_rules={ValidationRule.PERFORMANCE_IMPACT}) # Test high indicator count config.overlay_indicators = [f"indicator_{i}" for i in range(12)] # 12 indicators report = validator.validate_strategy_config(config) assert len(report.warnings) > 0 assert any("may impact performance" in str(warning) for warning in report.warnings) def test_indicator_conflicts_validation(self): """Test indicator conflicts validation.""" config = self.create_valid_config() validator = ConfigurationValidator(enabled_rules={ValidationRule.INDICATOR_CONFLICTS}) # Test multiple SMA indicators config.overlay_indicators = ["sma_5", "sma_10", "sma_20", "sma_50"] # 4 SMA indicators report = validator.validate_strategy_config(config) assert len(report.info) > 0 assert any("visual clutter" in str(info) for info in report.info) def test_resource_usage_validation(self): """Test resource usage validation.""" config = self.create_valid_config() validator = ConfigurationValidator(enabled_rules={ValidationRule.RESOURCE_USAGE}) # Test high memory usage configuration config.overlay_indicators = [f"indicator_{i}" for i in range(10)] config.subplot_configs = [ SubplotConfig(subplot_type=SubplotType.RSI, height_ratio=0.1, indicators=[]) for _ in range(10) ] # Many subplots report = validator.validate_strategy_config(config) assert len(report.warnings) > 0 or len(report.info) > 0 class TestValidationFunctions: """Test standalone validation functions.""" def create_test_config(self) -> StrategyChartConfig: """Create a test configuration.""" return StrategyChartConfig( strategy_name="Test Strategy", strategy_type=TradingStrategy.DAY_TRADING, description="Test strategy", timeframes=["15m", "1h"], main_chart_height=0.8, subplot_configs=[ SubplotConfig( subplot_type=SubplotType.RSI, height_ratio=0.2, indicators=[] ) ] ) def test_validate_configuration_function(self): """Test the standalone validate_configuration function.""" config = self.create_test_config() # Test with default rules report = validate_configuration(config) assert isinstance(report, ValidationReport) assert report.validation_time is not None # Test with specific rules specific_rules = {ValidationRule.REQUIRED_FIELDS, ValidationRule.HEIGHT_RATIOS} report = validate_configuration(config, rules=specific_rules) assert report.rules_applied == specific_rules # Test strict mode config.strategy_name = "AB" # Short name (should be warning) report = validate_configuration(config, strict=False) normal_errors = len(report.errors) report = validate_configuration(config, strict=True) strict_errors = len(report.errors) assert strict_errors >= normal_errors # Strict mode may have more errors def test_get_validation_rules_info(self): """Test getting validation rules information.""" rules_info = get_validation_rules_info() assert isinstance(rules_info, dict) assert len(rules_info) == len(ValidationRule) # Check that all rules have information for rule in ValidationRule: assert rule in rules_info rule_info = rules_info[rule] assert "name" in rule_info assert "description" in rule_info assert isinstance(rule_info["name"], str) assert isinstance(rule_info["description"], str) class TestValidationIntegration: """Test integration with existing systems.""" def test_strategy_config_validate_method(self): """Test the updated validate method in StrategyChartConfig.""" config = StrategyChartConfig( strategy_name="Integration Test", strategy_type=TradingStrategy.DAY_TRADING, description="Integration test strategy", timeframes=["15m"], main_chart_height=0.8, subplot_configs=[ SubplotConfig( subplot_type=SubplotType.RSI, height_ratio=0.2, indicators=[] ) ] ) # Test basic validate method (backward compatibility) is_valid, errors = config.validate() assert isinstance(is_valid, bool) assert isinstance(errors, list) # Test comprehensive validation method report = config.validate_comprehensive() assert isinstance(report, ValidationReport) assert report.validation_time is not None def test_validation_with_invalid_config(self): """Test validation with an invalid configuration.""" config = StrategyChartConfig( strategy_name="", # Invalid: empty name strategy_type=TradingStrategy.DAY_TRADING, description="", # Warning: empty description timeframes=[], # Invalid: no timeframes main_chart_height=1.5, # Invalid: > 1.0 subplot_configs=[ SubplotConfig( subplot_type=SubplotType.RSI, height_ratio=-0.1, # Invalid: negative indicators=[] ) ] ) # Test basic validation is_valid, errors = config.validate() assert not is_valid assert len(errors) > 0 # Test comprehensive validation report = config.validate_comprehensive() assert not report.is_valid assert len(report.errors) > 0 assert len(report.warnings) > 0 # Should have warnings too def test_validation_error_handling(self): """Test validation error handling.""" config = StrategyChartConfig( strategy_name="Error Test", strategy_type=TradingStrategy.DAY_TRADING, description="Error test strategy", timeframes=["15m"], main_chart_height=0.8, subplot_configs=[] ) # The validation should handle errors gracefully is_valid, errors = config.validate() assert isinstance(is_valid, bool) assert isinstance(errors, list) class TestValidationEdgeCases: """Test edge cases and boundary conditions.""" def test_empty_configuration(self): """Test validation with minimal configuration.""" config = StrategyChartConfig( strategy_name="Minimal", strategy_type=TradingStrategy.DAY_TRADING, description="Minimal config", timeframes=["1h"], overlay_indicators=[], subplot_configs=[] ) report = validate_configuration(config) # Should be valid even with minimal configuration assert isinstance(report, ValidationReport) def test_maximum_configuration(self): """Test validation with maximum complexity configuration.""" config = StrategyChartConfig( strategy_name="Maximum Complexity Strategy", strategy_type=TradingStrategy.DAY_TRADING, description="Strategy with maximum complexity for testing", timeframes=["1m", "5m", "15m", "1h", "4h"], main_chart_height=0.4, overlay_indicators=[f"indicator_{i}" for i in range(15)], subplot_configs=[ SubplotConfig( subplot_type=SubplotType.RSI, height_ratio=0.15, indicators=[f"rsi_{i}" for i in range(5)] ), SubplotConfig( subplot_type=SubplotType.MACD, height_ratio=0.15, indicators=[f"macd_{i}" for i in range(5)] ), SubplotConfig( subplot_type=SubplotType.VOLUME, height_ratio=0.1, indicators=[] ), SubplotConfig( subplot_type=SubplotType.MOMENTUM, height_ratio=0.2, indicators=[f"momentum_{i}" for i in range(3)] ) ] ) report = validate_configuration(config) # Should have warnings about performance and complexity assert len(report.warnings) > 0 or len(report.info) > 0 def test_boundary_values(self): """Test validation with boundary values.""" config = StrategyChartConfig( strategy_name="Boundary Test", strategy_type=TradingStrategy.DAY_TRADING, description="Boundary test strategy", timeframes=["1h"], main_chart_height=1.0, # Maximum allowed subplot_configs=[] # No subplots (total height = 1.0) ) report = validate_configuration(config) # Should be valid with exact boundary values assert isinstance(report, ValidationReport) if __name__ == "__main__": pytest.main([__file__])