""" Tests for Indicator Schema Validation System Tests the new indicator definition schema and validation functionality to ensure robust parameter validation and error handling. """ import pytest from typing import Dict, Any from components.charts.config.indicator_defs import ( IndicatorType, DisplayType, LineStyle, IndicatorParameterSchema, IndicatorSchema, ChartIndicatorConfig, INDICATOR_SCHEMAS, validate_indicator_configuration, create_indicator_config, get_indicator_schema, get_available_indicator_types, get_indicator_parameter_info, validate_parameters_for_type, create_configuration_from_json ) class TestIndicatorParameterSchema: """Test individual parameter schema validation.""" def test_required_parameter_validation(self): """Test validation of required parameters.""" schema = IndicatorParameterSchema( name="period", type=int, required=True, min_value=1, max_value=100 ) # Valid value is_valid, error = schema.validate(20) assert is_valid assert error == "" # Missing required parameter is_valid, error = schema.validate(None) assert not is_valid assert "required" in error.lower() # Wrong type is_valid, error = schema.validate("20") assert not is_valid assert "type" in error.lower() # Out of range is_valid, error = schema.validate(0) assert not is_valid assert ">=" in error is_valid, error = schema.validate(101) assert not is_valid assert "<=" in error def test_optional_parameter_validation(self): """Test validation of optional parameters.""" schema = IndicatorParameterSchema( name="price_column", type=str, required=False, default="close" ) # Valid value is_valid, error = schema.validate("high") assert is_valid # None is valid for optional is_valid, error = schema.validate(None) assert is_valid class TestIndicatorSchema: """Test complete indicator schema validation.""" def test_sma_schema_validation(self): """Test SMA indicator schema validation.""" schema = INDICATOR_SCHEMAS[IndicatorType.SMA] # Valid parameters params = {"period": 20, "price_column": "close"} is_valid, errors = schema.validate_parameters(params) assert is_valid assert len(errors) == 0 # Missing required parameter params = {"price_column": "close"} is_valid, errors = schema.validate_parameters(params) assert not is_valid assert any("period" in error and "required" in error for error in errors) # Invalid parameter value params = {"period": 0, "price_column": "close"} is_valid, errors = schema.validate_parameters(params) assert not is_valid assert any(">=" in error for error in errors) # Unknown parameter params = {"period": 20, "unknown_param": "test"} is_valid, errors = schema.validate_parameters(params) assert not is_valid assert any("unknown" in error.lower() for error in errors) def test_macd_schema_validation(self): """Test MACD indicator schema validation.""" schema = INDICATOR_SCHEMAS[IndicatorType.MACD] # Valid parameters params = { "fast_period": 12, "slow_period": 26, "signal_period": 9, "price_column": "close" } is_valid, errors = schema.validate_parameters(params) assert is_valid # Missing required parameters params = {"fast_period": 12} is_valid, errors = schema.validate_parameters(params) assert not is_valid assert len(errors) >= 2 # Missing slow_period and signal_period class TestChartIndicatorConfig: """Test chart indicator configuration validation.""" def test_valid_config_validation(self): """Test validation of a valid configuration.""" config = ChartIndicatorConfig( name="SMA (20)", indicator_type="sma", parameters={"period": 20, "price_column": "close"}, display_type="overlay", color="#007bff", line_style="solid", line_width=2, opacity=1.0, visible=True ) is_valid, errors = config.validate() assert is_valid assert len(errors) == 0 def test_invalid_indicator_type(self): """Test validation with invalid indicator type.""" config = ChartIndicatorConfig( name="Invalid Indicator", indicator_type="invalid_type", parameters={}, display_type="overlay", color="#007bff" ) is_valid, errors = config.validate() assert not is_valid assert any("unsupported indicator type" in error.lower() for error in errors) def test_invalid_display_properties(self): """Test validation of display properties.""" config = ChartIndicatorConfig( name="SMA (20)", indicator_type="sma", parameters={"period": 20}, display_type="invalid_display", color="#007bff", line_style="invalid_style", line_width=-1, opacity=2.0 ) is_valid, errors = config.validate() assert not is_valid # Check for multiple validation errors error_text = " ".join(errors).lower() assert "display_type" in error_text assert "line_style" in error_text assert "line_width" in error_text assert "opacity" in error_text class TestUtilityFunctions: """Test utility functions for indicator management.""" def test_create_indicator_config(self): """Test creating indicator configuration.""" config, errors = create_indicator_config( name="SMA (20)", indicator_type="sma", parameters={"period": 20}, color="#007bff" ) assert config is not None assert len(errors) == 0 assert config.name == "SMA (20)" assert config.indicator_type == "sma" assert config.parameters["period"] == 20 assert config.parameters["price_column"] == "close" # Default filled in def test_create_indicator_config_invalid(self): """Test creating invalid indicator configuration.""" config, errors = create_indicator_config( name="Invalid SMA", indicator_type="sma", parameters={"period": 0}, # Invalid period color="#007bff" ) assert config is None assert len(errors) > 0 assert any(">=" in error for error in errors) def test_get_indicator_schema(self): """Test getting indicator schema.""" schema = get_indicator_schema("sma") assert schema is not None assert schema.indicator_type == IndicatorType.SMA schema = get_indicator_schema("invalid_type") assert schema is None def test_get_available_indicator_types(self): """Test getting available indicator types.""" types = get_available_indicator_types() assert "sma" in types assert "ema" in types assert "rsi" in types assert "macd" in types assert "bollinger_bands" in types def test_get_indicator_parameter_info(self): """Test getting parameter information.""" info = get_indicator_parameter_info("sma") assert "period" in info assert info["period"]["type"] == "int" assert info["period"]["required"] assert "price_column" in info assert not info["price_column"]["required"] def test_validate_parameters_for_type(self): """Test parameter validation for specific type.""" is_valid, errors = validate_parameters_for_type("sma", {"period": 20}) assert is_valid is_valid, errors = validate_parameters_for_type("sma", {"period": 0}) assert not is_valid is_valid, errors = validate_parameters_for_type("invalid_type", {}) assert not is_valid def test_create_configuration_from_json(self): """Test creating configuration from JSON.""" json_data = { "name": "SMA (20)", "indicator_type": "sma", "parameters": {"period": 20}, "color": "#007bff" } config, errors = create_configuration_from_json(json_data) assert config is not None assert len(errors) == 0 # Test with JSON string import json json_string = json.dumps(json_data) config, errors = create_configuration_from_json(json_string) assert config is not None assert len(errors) == 0 # Test with missing fields invalid_json = {"name": "SMA"} config, errors = create_configuration_from_json(invalid_json) assert config is None assert len(errors) > 0 class TestIndicatorSchemaIntegration: """Test integration with existing indicator system.""" def test_schema_matches_built_in_indicators(self): """Test that schemas match built-in indicator definitions.""" from components.charts.config.indicator_defs import INDICATOR_DEFINITIONS for indicator_name, config in INDICATOR_DEFINITIONS.items(): # Validate each built-in configuration is_valid, errors = config.validate() if not is_valid: print(f"Validation errors for {indicator_name}: {errors}") assert is_valid, f"Built-in indicator {indicator_name} failed validation: {errors}" def test_parameter_schema_completeness(self): """Test that all indicator types have complete schemas.""" for indicator_type in IndicatorType: schema = INDICATOR_SCHEMAS.get(indicator_type) assert schema is not None, f"Missing schema for {indicator_type.value}" assert schema.indicator_type == indicator_type assert len(schema.required_parameters) > 0 or len(schema.optional_parameters) > 0 if __name__ == "__main__": pytest.main([__file__])