|
|
|
@@ -2,6 +2,7 @@
|
|
|
|
|
Unit test file for rpc/api_server.py
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
import json
|
|
|
|
|
from datetime import datetime, timedelta, timezone
|
|
|
|
|
from pathlib import Path
|
|
|
|
|
from unittest.mock import ANY, MagicMock, PropertyMock
|
|
|
|
@@ -16,7 +17,7 @@ from requests.auth import _basic_auth_str
|
|
|
|
|
|
|
|
|
|
from freqtrade.__init__ import __version__
|
|
|
|
|
from freqtrade.enums import RunMode, State
|
|
|
|
|
from freqtrade.exceptions import ExchangeError
|
|
|
|
|
from freqtrade.exceptions import DependencyException, ExchangeError, OperationalException
|
|
|
|
|
from freqtrade.loggers import setup_logging, setup_logging_pre
|
|
|
|
|
from freqtrade.persistence import PairLocks, Trade
|
|
|
|
|
from freqtrade.rpc import RPC
|
|
|
|
@@ -48,9 +49,13 @@ def botclient(default_conf, mocker):
|
|
|
|
|
ftbot = get_patched_freqtradebot(mocker, default_conf)
|
|
|
|
|
rpc = RPC(ftbot)
|
|
|
|
|
mocker.patch('freqtrade.rpc.api_server.ApiServer.start_api', MagicMock())
|
|
|
|
|
apiserver = ApiServer(rpc, default_conf)
|
|
|
|
|
yield ftbot, TestClient(apiserver.app)
|
|
|
|
|
# Cleanup ... ?
|
|
|
|
|
try:
|
|
|
|
|
apiserver = ApiServer(default_conf)
|
|
|
|
|
apiserver.add_rpc_handler(rpc)
|
|
|
|
|
yield ftbot, TestClient(apiserver.app)
|
|
|
|
|
# Cleanup ... ?
|
|
|
|
|
finally:
|
|
|
|
|
ApiServer.shutdown()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def client_post(client, url, data={}):
|
|
|
|
@@ -235,8 +240,13 @@ def test_api__init__(default_conf, mocker):
|
|
|
|
|
}})
|
|
|
|
|
mocker.patch('freqtrade.rpc.telegram.Updater', MagicMock())
|
|
|
|
|
mocker.patch('freqtrade.rpc.api_server.webserver.ApiServer.start_api', MagicMock())
|
|
|
|
|
apiserver = ApiServer(RPC(get_patched_freqtradebot(mocker, default_conf)), default_conf)
|
|
|
|
|
apiserver = ApiServer(default_conf)
|
|
|
|
|
apiserver.add_rpc_handler(RPC(get_patched_freqtradebot(mocker, default_conf)))
|
|
|
|
|
assert apiserver._config == default_conf
|
|
|
|
|
with pytest.raises(OperationalException, match="RPC Handler already attached."):
|
|
|
|
|
apiserver.add_rpc_handler(RPC(get_patched_freqtradebot(mocker, default_conf)))
|
|
|
|
|
|
|
|
|
|
ApiServer.shutdown()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_api_UvicornServer(mocker):
|
|
|
|
@@ -298,15 +308,21 @@ def test_api_run(default_conf, mocker, caplog):
|
|
|
|
|
}})
|
|
|
|
|
mocker.patch('freqtrade.rpc.telegram.Updater', MagicMock())
|
|
|
|
|
|
|
|
|
|
server_mock = MagicMock()
|
|
|
|
|
server_inst_mock = MagicMock()
|
|
|
|
|
server_inst_mock.run_in_thread = MagicMock()
|
|
|
|
|
server_inst_mock.run = MagicMock()
|
|
|
|
|
server_mock = MagicMock(return_value=server_inst_mock)
|
|
|
|
|
mocker.patch('freqtrade.rpc.api_server.webserver.UvicornServer', server_mock)
|
|
|
|
|
|
|
|
|
|
apiserver = ApiServer(RPC(get_patched_freqtradebot(mocker, default_conf)), default_conf)
|
|
|
|
|
apiserver = ApiServer(default_conf)
|
|
|
|
|
apiserver.add_rpc_handler(RPC(get_patched_freqtradebot(mocker, default_conf)))
|
|
|
|
|
|
|
|
|
|
assert server_mock.call_count == 1
|
|
|
|
|
assert apiserver._config == default_conf
|
|
|
|
|
apiserver.start_api()
|
|
|
|
|
assert server_mock.call_count == 2
|
|
|
|
|
assert server_inst_mock.run_in_thread.call_count == 2
|
|
|
|
|
assert server_inst_mock.run.call_count == 0
|
|
|
|
|
assert server_mock.call_args_list[0][0][0].host == "127.0.0.1"
|
|
|
|
|
assert server_mock.call_args_list[0][0][0].port == 8080
|
|
|
|
|
assert isinstance(server_mock.call_args_list[0][0][0].app, FastAPI)
|
|
|
|
@@ -325,6 +341,8 @@ def test_api_run(default_conf, mocker, caplog):
|
|
|
|
|
apiserver.start_api()
|
|
|
|
|
|
|
|
|
|
assert server_mock.call_count == 1
|
|
|
|
|
assert server_inst_mock.run_in_thread.call_count == 1
|
|
|
|
|
assert server_inst_mock.run.call_count == 0
|
|
|
|
|
assert server_mock.call_args_list[0][0][0].host == "0.0.0.0"
|
|
|
|
|
assert server_mock.call_args_list[0][0][0].port == 8089
|
|
|
|
|
assert isinstance(server_mock.call_args_list[0][0][0].app, FastAPI)
|
|
|
|
@@ -338,12 +356,24 @@ def test_api_run(default_conf, mocker, caplog):
|
|
|
|
|
"Please make sure that this is intentional!", caplog)
|
|
|
|
|
assert log_has_re("SECURITY WARNING - `jwt_secret_key` seems to be default.*", caplog)
|
|
|
|
|
|
|
|
|
|
server_mock.reset_mock()
|
|
|
|
|
apiserver._standalone = True
|
|
|
|
|
apiserver.start_api()
|
|
|
|
|
assert server_inst_mock.run_in_thread.call_count == 0
|
|
|
|
|
assert server_inst_mock.run.call_count == 1
|
|
|
|
|
|
|
|
|
|
apiserver1 = ApiServer(default_conf)
|
|
|
|
|
assert id(apiserver1) == id(apiserver)
|
|
|
|
|
|
|
|
|
|
apiserver._standalone = False
|
|
|
|
|
|
|
|
|
|
# Test crashing API server
|
|
|
|
|
caplog.clear()
|
|
|
|
|
mocker.patch('freqtrade.rpc.api_server.webserver.UvicornServer',
|
|
|
|
|
MagicMock(side_effect=Exception))
|
|
|
|
|
apiserver.start_api()
|
|
|
|
|
assert log_has("Api server failed to start.", caplog)
|
|
|
|
|
ApiServer.shutdown()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_api_cleanup(default_conf, mocker, caplog):
|
|
|
|
@@ -359,11 +389,13 @@ def test_api_cleanup(default_conf, mocker, caplog):
|
|
|
|
|
server_mock.cleanup = MagicMock()
|
|
|
|
|
mocker.patch('freqtrade.rpc.api_server.webserver.UvicornServer', server_mock)
|
|
|
|
|
|
|
|
|
|
apiserver = ApiServer(RPC(get_patched_freqtradebot(mocker, default_conf)), default_conf)
|
|
|
|
|
apiserver = ApiServer(default_conf)
|
|
|
|
|
apiserver.add_rpc_handler(RPC(get_patched_freqtradebot(mocker, default_conf)))
|
|
|
|
|
|
|
|
|
|
apiserver.cleanup()
|
|
|
|
|
assert apiserver._server.cleanup.call_count == 1
|
|
|
|
|
assert log_has("Stopping API Server", caplog)
|
|
|
|
|
ApiServer.shutdown()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_api_reloadconf(botclient):
|
|
|
|
@@ -1221,3 +1253,108 @@ def test_list_available_pairs(botclient):
|
|
|
|
|
assert rc.json()['length'] == 1
|
|
|
|
|
assert rc.json()['pairs'] == ['XRP/ETH']
|
|
|
|
|
assert len(rc.json()['pair_interval']) == 1
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_api_backtesting(botclient, mocker, fee, caplog):
|
|
|
|
|
ftbot, client = botclient
|
|
|
|
|
mocker.patch('freqtrade.exchange.Exchange.get_fee', fee)
|
|
|
|
|
|
|
|
|
|
# Backtesting not started yet
|
|
|
|
|
rc = client_get(client, f"{BASE_URI}/backtest")
|
|
|
|
|
assert_response(rc)
|
|
|
|
|
|
|
|
|
|
result = rc.json()
|
|
|
|
|
assert result['status'] == 'not_started'
|
|
|
|
|
assert not result['running']
|
|
|
|
|
assert result['status_msg'] == 'Backtest not yet executed'
|
|
|
|
|
assert result['progress'] == 0
|
|
|
|
|
|
|
|
|
|
# Reset backtesting
|
|
|
|
|
rc = client_delete(client, f"{BASE_URI}/backtest")
|
|
|
|
|
assert_response(rc)
|
|
|
|
|
result = rc.json()
|
|
|
|
|
assert result['status'] == 'reset'
|
|
|
|
|
assert not result['running']
|
|
|
|
|
assert result['status_msg'] == 'Backtest reset'
|
|
|
|
|
|
|
|
|
|
# start backtesting
|
|
|
|
|
data = {
|
|
|
|
|
"strategy": "DefaultStrategy",
|
|
|
|
|
"timeframe": "5m",
|
|
|
|
|
"timerange": "20180110-20180111",
|
|
|
|
|
"max_open_trades": 3,
|
|
|
|
|
"stake_amount": 100,
|
|
|
|
|
"dry_run_wallet": 1000,
|
|
|
|
|
"enable_protections": False
|
|
|
|
|
}
|
|
|
|
|
rc = client_post(client, f"{BASE_URI}/backtest", data=json.dumps(data))
|
|
|
|
|
assert_response(rc)
|
|
|
|
|
result = rc.json()
|
|
|
|
|
|
|
|
|
|
assert result['status'] == 'running'
|
|
|
|
|
assert result['progress'] == 0
|
|
|
|
|
assert result['running']
|
|
|
|
|
assert result['status_msg'] == 'Backtest started'
|
|
|
|
|
|
|
|
|
|
rc = client_get(client, f"{BASE_URI}/backtest")
|
|
|
|
|
assert_response(rc)
|
|
|
|
|
|
|
|
|
|
result = rc.json()
|
|
|
|
|
assert result['status'] == 'ended'
|
|
|
|
|
assert not result['running']
|
|
|
|
|
assert result['status_msg'] == 'Backtest ended'
|
|
|
|
|
assert result['progress'] == 1
|
|
|
|
|
assert result['backtest_result']
|
|
|
|
|
|
|
|
|
|
rc = client_get(client, f"{BASE_URI}/backtest/abort")
|
|
|
|
|
assert_response(rc)
|
|
|
|
|
result = rc.json()
|
|
|
|
|
assert result['status'] == 'not_running'
|
|
|
|
|
assert not result['running']
|
|
|
|
|
assert result['status_msg'] == 'Backtest ended'
|
|
|
|
|
|
|
|
|
|
# Simulate running backtest
|
|
|
|
|
ApiServer._bgtask_running = True
|
|
|
|
|
rc = client_get(client, f"{BASE_URI}/backtest/abort")
|
|
|
|
|
assert_response(rc)
|
|
|
|
|
result = rc.json()
|
|
|
|
|
assert result['status'] == 'stopping'
|
|
|
|
|
assert not result['running']
|
|
|
|
|
assert result['status_msg'] == 'Backtest ended'
|
|
|
|
|
|
|
|
|
|
# Get running backtest...
|
|
|
|
|
rc = client_get(client, f"{BASE_URI}/backtest")
|
|
|
|
|
assert_response(rc)
|
|
|
|
|
result = rc.json()
|
|
|
|
|
assert result['status'] == 'running'
|
|
|
|
|
assert result['running']
|
|
|
|
|
assert result['step'] == "backtest"
|
|
|
|
|
assert result['status_msg'] == "Backtest running"
|
|
|
|
|
|
|
|
|
|
# Try delete with task still running
|
|
|
|
|
rc = client_delete(client, f"{BASE_URI}/backtest")
|
|
|
|
|
assert_response(rc)
|
|
|
|
|
result = rc.json()
|
|
|
|
|
assert result['status'] == 'running'
|
|
|
|
|
|
|
|
|
|
# Post to backtest that's still running
|
|
|
|
|
rc = client_post(client, f"{BASE_URI}/backtest", data=json.dumps(data))
|
|
|
|
|
assert_response(rc, 502)
|
|
|
|
|
result = rc.json()
|
|
|
|
|
assert 'Bot Background task already running' in result['error']
|
|
|
|
|
|
|
|
|
|
ApiServer._bgtask_running = False
|
|
|
|
|
|
|
|
|
|
mocker.patch('freqtrade.optimize.backtesting.Backtesting.backtest_one_strategy',
|
|
|
|
|
side_effect=DependencyException())
|
|
|
|
|
rc = client_post(client, f"{BASE_URI}/backtest", data=json.dumps(data))
|
|
|
|
|
assert log_has("Backtesting caused an error: ", caplog)
|
|
|
|
|
|
|
|
|
|
# Delete backtesting to avoid leakage since the backtest-object may stick around.
|
|
|
|
|
rc = client_delete(client, f"{BASE_URI}/backtest")
|
|
|
|
|
assert_response(rc)
|
|
|
|
|
|
|
|
|
|
result = rc.json()
|
|
|
|
|
assert result['status'] == 'reset'
|
|
|
|
|
assert not result['running']
|
|
|
|
|
assert result['status_msg'] == 'Backtest reset'
|
|
|
|
|