TCPDashboard/tests/config/strategies/test_config_utils.py

383 lines
15 KiB
Python
Raw Normal View History

"""
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