""" Tests for strategy configuration utilities. """ import pytest import json import tempfile import os from pathlib import Path from unittest.mock import patch, mock_open from config.strategies.config_utils import ( load_strategy_templates, get_strategy_dropdown_options, get_strategy_parameter_schema, get_strategy_default_parameters, get_strategy_metadata, get_strategy_required_indicators, generate_parameter_fields_config, validate_strategy_parameters, save_user_strategy, load_user_strategies, delete_user_strategy, export_strategy_config, import_strategy_config ) class TestLoadStrategyTemplates: """Tests for template loading functionality.""" @patch('os.path.exists') @patch('os.listdir') @patch('builtins.open', new_callable=mock_open) def test_load_templates_success(self, mock_file, mock_listdir, mock_exists): """Test successful template loading.""" mock_exists.return_value = True mock_listdir.return_value = ['ema_crossover_template.json', 'rsi_template.json'] # Mock template content template_data = { 'type': 'ema_crossover', 'name': 'EMA Crossover', 'parameter_schema': {'fast_period': {'type': 'int', 'default': 12}} } mock_file.return_value.read.return_value = json.dumps(template_data) templates = load_strategy_templates() assert 'ema_crossover' in templates assert templates['ema_crossover']['name'] == 'EMA Crossover' @patch('os.path.exists') def test_load_templates_no_directory(self, mock_exists): """Test loading when template directory doesn't exist.""" mock_exists.return_value = False templates = load_strategy_templates() assert templates == {} @patch('os.path.exists') @patch('os.listdir') @patch('builtins.open', new_callable=mock_open) def test_load_templates_invalid_json(self, mock_file, mock_listdir, mock_exists): """Test loading with invalid JSON.""" mock_exists.return_value = True mock_listdir.return_value = ['invalid_template.json'] mock_file.return_value.read.return_value = 'invalid json' templates = load_strategy_templates() assert templates == {} class TestGetStrategyDropdownOptions: """Tests for dropdown options generation.""" @patch('config.strategies.config_utils.load_strategy_templates') def test_dropdown_options_success(self, mock_load_templates): """Test successful dropdown options generation.""" mock_load_templates.return_value = { 'ema_crossover': {'name': 'EMA Crossover'}, 'rsi': {'name': 'RSI Strategy'} } options = get_strategy_dropdown_options() assert len(options) == 2 assert {'label': 'EMA Crossover', 'value': 'ema_crossover'} in options assert {'label': 'RSI Strategy', 'value': 'rsi'} in options @patch('config.strategies.config_utils.load_strategy_templates') def test_dropdown_options_empty(self, mock_load_templates): """Test dropdown options with no templates.""" mock_load_templates.return_value = {} options = get_strategy_dropdown_options() assert options == [] class TestParameterValidation: """Tests for parameter validation functionality.""" def test_validate_ema_crossover_parameters_valid(self): """Test validation of valid EMA crossover parameters.""" # Create a mock template for testing with patch('config.strategies.config_utils.get_strategy_parameter_schema') as mock_schema: mock_schema.return_value = { 'fast_period': {'type': 'int', 'min': 5, 'max': 50, 'required': True}, 'slow_period': {'type': 'int', 'min': 10, 'max': 200, 'required': True} } parameters = {'fast_period': 12, 'slow_period': 26} is_valid, errors = validate_strategy_parameters('ema_crossover', parameters) assert is_valid assert errors == [] def test_validate_ema_crossover_parameters_invalid(self): """Test validation of invalid EMA crossover parameters.""" with patch('config.strategies.config_utils.get_strategy_parameter_schema') as mock_schema: mock_schema.return_value = { 'fast_period': {'type': 'int', 'min': 5, 'max': 50, 'required': True}, 'slow_period': {'type': 'int', 'min': 10, 'max': 200, 'required': True} } parameters = {'fast_period': 100} # Missing slow_period, fast_period out of range is_valid, errors = validate_strategy_parameters('ema_crossover', parameters) assert not is_valid assert len(errors) >= 2 # Should have errors for both issues def test_validate_rsi_parameters_valid(self): """Test validation of valid RSI parameters.""" with patch('config.strategies.config_utils.get_strategy_parameter_schema') as mock_schema: mock_schema.return_value = { 'period': {'type': 'int', 'min': 2, 'max': 50, 'required': True}, 'overbought': {'type': 'float', 'min': 50.0, 'max': 95.0, 'required': True}, 'oversold': {'type': 'float', 'min': 5.0, 'max': 50.0, 'required': True} } parameters = {'period': 14, 'overbought': 70.0, 'oversold': 30.0} is_valid, errors = validate_strategy_parameters('rsi', parameters) assert is_valid assert errors == [] def test_validate_parameters_no_schema(self): """Test validation when no schema is found.""" with patch('config.strategies.config_utils.get_strategy_parameter_schema') as mock_schema: mock_schema.return_value = None parameters = {'any_param': 'any_value'} is_valid, errors = validate_strategy_parameters('unknown_strategy', parameters) assert not is_valid assert 'No schema found' in str(errors) class TestUserStrategyManagement: """Tests for user strategy file management.""" def test_save_user_strategy_success(self): """Test successful saving of user strategy.""" with tempfile.TemporaryDirectory() as temp_dir: with patch('config.strategies.config_utils.os.path.dirname') as mock_dirname: mock_dirname.return_value = temp_dir config = { 'name': 'My EMA Strategy', 'strategy': 'ema_crossover', 'fast_period': 12, 'slow_period': 26 } result = save_user_strategy('My EMA Strategy', config) assert result # Check file was created expected_file = Path(temp_dir) / 'user_strategies' / 'my_ema_strategy.json' assert expected_file.exists() def test_save_user_strategy_error(self): """Test error handling during strategy saving.""" with patch('builtins.open', mock_open()) as mock_file: mock_file.side_effect = IOError("Permission denied") config = {'name': 'Test Strategy'} result = save_user_strategy('Test Strategy', config) assert not result def test_load_user_strategies_success(self): """Test successful loading of user strategies.""" with tempfile.TemporaryDirectory() as temp_dir: # Create test strategy file user_strategies_dir = Path(temp_dir) / 'user_strategies' user_strategies_dir.mkdir() strategy_file = user_strategies_dir / 'test_strategy.json' strategy_data = { 'name': 'Test Strategy', 'strategy': 'ema_crossover', 'parameters': {'fast_period': 12} } with open(strategy_file, 'w') as f: json.dump(strategy_data, f) with patch('config.strategies.config_utils.os.path.dirname') as mock_dirname: mock_dirname.return_value = temp_dir strategies = load_user_strategies() assert 'Test Strategy' in strategies assert strategies['Test Strategy']['strategy'] == 'ema_crossover' def test_load_user_strategies_no_directory(self): """Test loading when user strategies directory doesn't exist.""" with tempfile.TemporaryDirectory() as temp_dir: with patch('config.strategies.config_utils.os.path.dirname') as mock_dirname: mock_dirname.return_value = temp_dir strategies = load_user_strategies() assert strategies == {} def test_delete_user_strategy_success(self): """Test successful deletion of user strategy.""" with tempfile.TemporaryDirectory() as temp_dir: # Create test strategy file user_strategies_dir = Path(temp_dir) / 'user_strategies' user_strategies_dir.mkdir() strategy_file = user_strategies_dir / 'test_strategy.json' strategy_file.write_text('{}') with patch('config.strategies.config_utils.os.path.dirname') as mock_dirname: mock_dirname.return_value = temp_dir result = delete_user_strategy('Test Strategy') assert result assert not strategy_file.exists() def test_delete_user_strategy_not_found(self): """Test deletion of non-existent strategy.""" with tempfile.TemporaryDirectory() as temp_dir: with patch('config.strategies.config_utils.os.path.dirname') as mock_dirname: mock_dirname.return_value = temp_dir result = delete_user_strategy('Non Existent Strategy') assert not result class TestStrategyConfigImportExport: """Tests for strategy configuration import/export functionality.""" def test_export_strategy_config(self): """Test exporting strategy configuration.""" config = { 'strategy': 'ema_crossover', 'fast_period': 12, 'slow_period': 26 } result = export_strategy_config('My Strategy', config) # Parse the exported JSON exported_data = json.loads(result) assert exported_data['name'] == 'My Strategy' assert exported_data['config'] == config assert 'exported_at' in exported_data assert 'version' in exported_data def test_import_strategy_config_success(self): """Test successful import of strategy configuration.""" import_data = { 'name': 'Imported Strategy', 'config': { 'strategy': 'ema_crossover', 'fast_period': 12, 'slow_period': 26 }, 'version': '1.0' } json_string = json.dumps(import_data) with patch('config.strategies.config_utils.validate_strategy_parameters') as mock_validate: mock_validate.return_value = (True, []) success, data, errors = import_strategy_config(json_string) assert success assert data['name'] == 'Imported Strategy' assert errors == [] def test_import_strategy_config_invalid_json(self): """Test import with invalid JSON.""" json_string = 'invalid json' success, data, errors = import_strategy_config(json_string) assert not success assert data is None assert len(errors) > 0 assert 'Invalid JSON format' in str(errors) def test_import_strategy_config_missing_fields(self): """Test import with missing required fields.""" import_data = {'name': 'Test Strategy'} # Missing 'config' json_string = json.dumps(import_data) success, data, errors = import_strategy_config(json_string) assert not success assert data is None assert 'missing name or config fields' in str(errors) def test_import_strategy_config_invalid_parameters(self): """Test import with invalid strategy parameters.""" import_data = { 'name': 'Invalid Strategy', 'config': { 'strategy': 'ema_crossover', 'fast_period': 'invalid' # Should be int } } json_string = json.dumps(import_data) with patch('config.strategies.config_utils.validate_strategy_parameters') as mock_validate: mock_validate.return_value = (False, ['Invalid parameter type']) success, data, errors = import_strategy_config(json_string) assert not success assert data is None assert 'Invalid parameter type' in str(errors) class TestParameterFieldsConfig: """Tests for parameter fields configuration generation.""" def test_generate_parameter_fields_config_success(self): """Test successful generation of parameter fields configuration.""" with patch('config.strategies.config_utils.get_strategy_parameter_schema') as mock_schema, \ patch('config.strategies.config_utils.get_strategy_default_parameters') as mock_defaults: mock_schema.return_value = { 'fast_period': { 'type': 'int', 'description': 'Fast EMA period', 'min': 5, 'max': 50, 'default': 12 } } mock_defaults.return_value = {'fast_period': 12} config = generate_parameter_fields_config('ema_crossover') assert 'fast_period' in config field_config = config['fast_period'] assert field_config['type'] == 'int' assert field_config['label'] == 'Fast Period' assert field_config['default'] == 12 assert field_config['min'] == 5 assert field_config['max'] == 50 def test_generate_parameter_fields_config_no_schema(self): """Test parameter fields config when no schema exists.""" with patch('config.strategies.config_utils.get_strategy_parameter_schema') as mock_schema: mock_schema.return_value = None config = generate_parameter_fields_config('unknown_strategy') assert config is None