diff --git a/freqtrade/misc.py b/freqtrade/misc.py index 7cde85813..2bd983137 100644 --- a/freqtrade/misc.py +++ b/freqtrade/misc.py @@ -6,7 +6,8 @@ import os import time from typing import Any, Callable, List, Dict -from jsonschema import validate +from jsonschema import validate, Draft4Validator +from jsonschema.exceptions import best_match, ValidationError from wrapt import synchronized from freqtrade import __version__ @@ -58,8 +59,14 @@ def load_config(path: str) -> Dict: if 'internals' not in conf: conf['internals'] = {} logger.info('Validating configuration ...') - validate(conf, CONF_SCHEMA) - return conf + try: + validate(conf, CONF_SCHEMA) + return conf + except ValidationError: + logger.fatal('Configuration is not valid! See config.json.example') + raise ValidationError( + best_match(Draft4Validator(CONF_SCHEMA).iter_errors(conf)).message + ) def throttle(func: Callable[..., Any], min_secs: float, *args, **kwargs) -> Any: @@ -218,7 +225,10 @@ CONF_SCHEMA = { 'secret': {'type': 'string'}, 'pair_whitelist': { 'type': 'array', - 'items': {'type': 'string'}, + 'items': { + 'type': 'string', + 'pattern': '^[0-9A-Z]+_[0-9A-Z]+$' + }, 'uniqueItems': True } }, diff --git a/freqtrade/tests/test_misc.py b/freqtrade/tests/test_misc.py index 35ebf0979..78507f226 100644 --- a/freqtrade/tests/test_misc.py +++ b/freqtrade/tests/test_misc.py @@ -1,12 +1,15 @@ # pragma pylint: disable=missing-docstring,C0103 -import time +import json import os +import time from argparse import Namespace +from copy import deepcopy from unittest.mock import MagicMock import pytest +from jsonschema import ValidationError -from freqtrade.misc import throttle, parse_args, start_backtesting +from freqtrade.misc import throttle, parse_args, start_backtesting, load_config def test_throttle(): @@ -119,3 +122,28 @@ def test_start_backtesting(mocker): main_call_args = pytest_mock.call_args[0][0] assert main_call_args[0] == '-s' assert main_call_args[1].endswith(os.path.join('freqtrade', 'tests', 'test_backtesting.py')) + + +def test_load_config(default_conf, mocker): + file_mock = mocker.patch('freqtrade.misc.open', mocker.mock_open( + read_data=json.dumps(default_conf) + )) + validated_conf = load_config('somefile') + assert file_mock.call_count == 1 + assert validated_conf.items() >= default_conf.items() + + +def test_load_config_invalid_pair(default_conf, mocker): + conf = deepcopy(default_conf) + conf['exchange']['pair_whitelist'].append('BTC-ETH') + mocker.patch('freqtrade.misc.open', mocker.mock_open(read_data=json.dumps(conf))) + with pytest.raises(ValidationError, match=r'.*does not match.*'): + load_config('somefile') + + +def test_load_config_missing_attributes(default_conf, mocker): + conf = deepcopy(default_conf) + conf.pop('exchange') + mocker.patch('freqtrade.misc.open', mocker.mock_open(read_data=json.dumps(conf))) + with pytest.raises(ValidationError, match=r'.*\'exchange\' is a required property.*'): + load_config('somefile')