TCPDashboard/tests/test_validation.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

539 lines
21 KiB
Python

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