stable/freqtrade/tests/rpc/test_rpc_apiserver.py

407 lines
13 KiB
Python
Raw Normal View History

2019-04-04 05:12:58 +00:00
"""
Unit test file for rpc/api_server.py
"""
2019-05-11 11:31:48 +00:00
from datetime import datetime
2019-05-11 12:05:25 +00:00
from unittest.mock import ANY, MagicMock, PropertyMock
2019-04-04 05:12:58 +00:00
2019-05-10 05:07:38 +00:00
import pytest
2019-05-11 07:44:39 +00:00
from freqtrade.__init__ import __version__
2019-05-14 05:07:51 +00:00
from freqtrade.persistence import Trade
2019-04-04 05:12:58 +00:00
from freqtrade.rpc.api_server import ApiServer
from freqtrade.state import State
2019-05-14 05:07:51 +00:00
from freqtrade.tests.conftest import (get_patched_freqtradebot, log_has,
patch_get_signal)
2019-04-04 05:12:58 +00:00
2019-05-10 05:07:38 +00:00
@pytest.fixture
2019-05-11 06:55:21 +00:00
def botclient(default_conf, mocker):
2019-05-11 11:31:48 +00:00
default_conf.update({"api_server": {"enabled": True,
2019-05-11 11:40:30 +00:00
"listen_ip_address": "127.0.0.1",
"listen_port": "8080"}})
2019-05-11 06:55:21 +00:00
ftbot = get_patched_freqtradebot(mocker, default_conf)
2019-05-11 07:44:39 +00:00
mocker.patch('freqtrade.rpc.api_server.ApiServer.run', MagicMock())
2019-05-11 06:55:21 +00:00
apiserver = ApiServer(ftbot)
yield ftbot, apiserver.app.test_client()
2019-05-10 05:07:38 +00:00
# Cleanup ... ?
2019-05-11 12:05:25 +00:00
def assert_response(response, expected_code=200):
assert response.status_code == expected_code
2019-05-10 05:07:38 +00:00
assert response.content_type == "application/json"
2019-05-11 11:18:11 +00:00
def test_api_not_found(botclient):
ftbot, client = botclient
rc = client.post("/invalid_url")
2019-05-11 12:05:25 +00:00
assert_response(rc, 404)
2019-05-11 11:18:11 +00:00
assert rc.json == {'status': 'error',
'reason': "There's no API call for http://localhost/invalid_url.",
'code': 404
}
2019-05-11 06:55:21 +00:00
def test_api_stop_workflow(botclient):
ftbot, client = botclient
assert ftbot.state == State.RUNNING
rc = client.post("/stop")
2019-05-11 12:05:25 +00:00
assert_response(rc)
2019-05-11 06:55:21 +00:00
assert rc.json == {'status': 'stopping trader ...'}
assert ftbot.state == State.STOPPED
2019-05-10 05:07:38 +00:00
2019-05-11 06:55:21 +00:00
# Stop bot again
2019-05-10 05:07:38 +00:00
rc = client.post("/stop")
2019-05-11 12:05:25 +00:00
assert_response(rc)
2019-05-11 06:55:21 +00:00
assert rc.json == {'status': 'already stopped'}
# Start bot
rc = client.post("/start")
2019-05-11 12:05:25 +00:00
assert_response(rc)
2019-05-11 06:55:21 +00:00
assert rc.json == {'status': 'starting trader ...'}
assert ftbot.state == State.RUNNING
# Call start again
rc = client.post("/start")
2019-05-11 12:05:25 +00:00
assert_response(rc)
2019-05-11 06:55:21 +00:00
assert rc.json == {'status': 'already running'}
2019-05-10 05:07:38 +00:00
2019-05-11 06:55:21 +00:00
def test_api__init__(default_conf, mocker):
2019-04-04 05:12:58 +00:00
"""
Test __init__() method
"""
mocker.patch('freqtrade.rpc.telegram.Updater', MagicMock())
mocker.patch('freqtrade.rpc.api_server.ApiServer.run', MagicMock())
apiserver = ApiServer(get_patched_freqtradebot(mocker, default_conf))
assert apiserver._config == default_conf
2019-05-14 05:07:51 +00:00
def test_api_run(default_conf, mocker, caplog):
default_conf.update({"api_server": {"enabled": True,
"listen_ip_address": "127.0.0.1",
"listen_port": "8080"}})
mocker.patch('freqtrade.rpc.telegram.Updater', MagicMock())
mocker.patch('freqtrade.rpc.api_server.threading.Thread', MagicMock())
apiserver = ApiServer(get_patched_freqtradebot(mocker, default_conf))
# Monkey patch flask app
run_mock = MagicMock()
apiserver.app = MagicMock()
apiserver.app.run = run_mock
assert apiserver._config == default_conf
apiserver.run()
assert run_mock.call_count == 1
assert run_mock.call_args_list[0][1]["host"] == "127.0.0.1"
assert run_mock.call_args_list[0][1]["port"] == "8080"
assert log_has("Starting HTTP Server at 127.0.0.1:8080", caplog.record_tuples)
assert log_has("Starting Local Rest Server", caplog.record_tuples)
# Test binding to public
caplog.clear()
run_mock.reset_mock()
apiserver._config.update({"api_server": {"enabled": True,
"listen_ip_address": "0.0.0.0",
"listen_port": "8089"}})
apiserver.run()
assert run_mock.call_count == 1
assert run_mock.call_args_list[0][1]["host"] == "0.0.0.0"
assert run_mock.call_args_list[0][1]["port"] == "8089"
assert log_has("Starting HTTP Server at 0.0.0.0:8089", caplog.record_tuples)
assert log_has("Starting Local Rest Server", caplog.record_tuples)
assert log_has("SECURITY WARNING - Local Rest Server listening to external connections",
caplog.record_tuples)
assert log_has("SECURITY WARNING - This is insecure please set to your loopback,"
"e.g 127.0.0.1 in config.json",
caplog.record_tuples)
2019-05-15 04:24:22 +00:00
# Test crashing flask
caplog.clear()
apiserver.app.run = MagicMock(side_effect=Exception)
apiserver.run()
assert log_has("Api server failed to start, exception message is:",
caplog.record_tuples)
2019-05-14 05:07:51 +00:00
2019-05-11 06:55:21 +00:00
def test_api_reloadconf(botclient):
ftbot, client = botclient
2019-04-04 05:12:58 +00:00
2019-05-11 06:55:21 +00:00
rc = client.post("/reload_conf")
2019-05-11 12:05:25 +00:00
assert_response(rc)
2019-05-11 06:55:21 +00:00
assert rc.json == {'status': 'reloading config ...'}
assert ftbot.state == State.RELOAD_CONF
2019-04-04 05:12:58 +00:00
2019-05-11 06:55:21 +00:00
def test_api_stopbuy(botclient):
ftbot, client = botclient
assert ftbot.config['max_open_trades'] != 0
2019-04-04 05:12:58 +00:00
2019-05-11 06:55:21 +00:00
rc = client.post("/stopbuy")
2019-05-11 12:05:25 +00:00
assert_response(rc)
2019-05-11 06:55:21 +00:00
assert rc.json == {'status': 'No more buy will occur from now. Run /reload_conf to reset.'}
assert ftbot.config['max_open_trades'] == 0
2019-05-11 07:10:54 +00:00
def test_api_balance(botclient, mocker, rpc_balance):
ftbot, client = botclient
def mock_ticker(symbol, refresh):
if symbol == 'BTC/USDT':
return {
'bid': 10000.00,
'ask': 10000.00,
'last': 10000.00,
}
elif symbol == 'XRP/BTC':
return {
'bid': 0.00001,
'ask': 0.00001,
'last': 0.00001,
}
return {
'bid': 0.1,
'ask': 0.1,
'last': 0.1,
}
mocker.patch('freqtrade.exchange.Exchange.get_balances', return_value=rpc_balance)
mocker.patch('freqtrade.exchange.Exchange.get_ticker', side_effect=mock_ticker)
rc = client.get("/balance")
2019-05-11 12:05:25 +00:00
assert_response(rc)
2019-05-11 07:10:54 +00:00
assert "currencies" in rc.json
assert len(rc.json["currencies"]) == 5
assert rc.json['currencies'][0] == {
'currency': 'BTC',
'available': 12.0,
'balance': 12.0,
'pending': 0.0,
'est_btc': 12.0,
}
2019-05-11 07:44:39 +00:00
def test_api_count(botclient, mocker, ticker, fee, markets):
ftbot, client = botclient
patch_get_signal(ftbot, (True, False))
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
get_balances=MagicMock(return_value=ticker),
get_ticker=ticker,
get_fee=fee,
markets=PropertyMock(return_value=markets)
)
rc = client.get("/count")
2019-05-11 12:05:25 +00:00
assert_response(rc)
2019-05-11 07:44:39 +00:00
assert rc.json["current"] == 0
assert rc.json["max"] == 1.0
# Create some test data
ftbot.create_trade()
rc = client.get("/count")
2019-05-11 12:05:25 +00:00
assert_response(rc)
2019-05-11 07:44:39 +00:00
assert rc.json["current"] == 1.0
assert rc.json["max"] == 1.0
2019-05-11 11:31:48 +00:00
def test_api_daily(botclient, mocker, ticker, fee, markets):
ftbot, client = botclient
patch_get_signal(ftbot, (True, False))
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
get_balances=MagicMock(return_value=ticker),
get_ticker=ticker,
get_fee=fee,
markets=PropertyMock(return_value=markets)
)
rc = client.get("/daily")
2019-05-11 12:05:25 +00:00
assert_response(rc)
2019-05-11 11:31:48 +00:00
assert len(rc.json) == 7
assert rc.json[0][0] == str(datetime.utcnow().date())
2019-05-11 12:05:25 +00:00
def test_api_edge_disabled(botclient, mocker, ticker, fee, markets):
ftbot, client = botclient
patch_get_signal(ftbot, (True, False))
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
get_balances=MagicMock(return_value=ticker),
get_ticker=ticker,
get_fee=fee,
markets=PropertyMock(return_value=markets)
)
rc = client.get("/edge")
assert_response(rc, 502)
assert rc.json == {"error": "Error querying _edge: Edge is not enabled."}
def test_api_profit(botclient, mocker, ticker, fee, markets, limit_buy_order, limit_sell_order):
ftbot, client = botclient
patch_get_signal(ftbot, (True, False))
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
get_balances=MagicMock(return_value=ticker),
get_ticker=ticker,
get_fee=fee,
markets=PropertyMock(return_value=markets)
)
rc = client.get("/profit")
assert_response(rc, 502)
assert len(rc.json) == 1
assert rc.json == {"error": "Error querying _profit: no closed trade"}
ftbot.create_trade()
trade = Trade.query.first()
# Simulate fulfilled LIMIT_BUY order for trade
trade.update(limit_buy_order)
rc = client.get("/profit")
assert_response(rc, 502)
assert rc.json == {"error": "Error querying _profit: no closed trade"}
trade.update(limit_sell_order)
trade.close_date = datetime.utcnow()
trade.is_open = False
rc = client.get("/profit")
assert_response(rc)
assert rc.json == {'avg_duration': '0:00:00',
'best_pair': 'ETH/BTC',
'best_rate': 6.2,
'first_trade_date': 'just now',
'latest_trade_date': 'just now',
'profit_all_coin': 6.217e-05,
'profit_all_fiat': 0,
'profit_all_percent': 6.2,
'profit_closed_coin': 6.217e-05,
'profit_closed_fiat': 0,
'profit_closed_percent': 6.2,
'trade_count': 1
}
def test_api_performance(botclient, mocker, ticker, fee, markets):
ftbot, client = botclient
patch_get_signal(ftbot, (True, False))
trade = Trade(
pair='LTC/ETH',
amount=1,
exchange='binance',
stake_amount=1,
open_rate=0.245441,
open_order_id="123456",
is_open=False,
fee_close=fee.return_value,
fee_open=fee.return_value,
close_rate=0.265441,
)
trade.close_profit = trade.calc_profit_percent()
Trade.session.add(trade)
trade = Trade(
pair='XRP/ETH',
amount=5,
stake_amount=1,
exchange='binance',
open_rate=0.412,
open_order_id="123456",
is_open=False,
fee_close=fee.return_value,
fee_open=fee.return_value,
close_rate=0.391
)
trade.close_profit = trade.calc_profit_percent()
Trade.session.add(trade)
Trade.session.flush()
rc = client.get("/performance")
assert_response(rc)
assert len(rc.json) == 2
assert rc.json == [{'count': 1, 'pair': 'LTC/ETH', 'profit': 7.61},
{'count': 1, 'pair': 'XRP/ETH', 'profit': -5.57}]
def test_api_status(botclient, mocker, ticker, fee, markets, limit_buy_order, limit_sell_order):
ftbot, client = botclient
patch_get_signal(ftbot, (True, False))
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
get_balances=MagicMock(return_value=ticker),
get_ticker=ticker,
get_fee=fee,
markets=PropertyMock(return_value=markets)
)
rc = client.get("/status")
assert_response(rc, 502)
assert rc.json == {'error': 'Error querying _status: no active trade'}
ftbot.create_trade()
rc = client.get("/status")
assert_response(rc)
assert len(rc.json) == 1
assert rc.json == [{'amount': 90.99181074,
'base_currency': 'BTC',
'close_date': None,
'close_date_hum': None,
'close_profit': None,
'close_rate': None,
'current_profit': -0.59,
'current_rate': 1.098e-05,
'initial_stop_loss': 0.0,
'initial_stop_loss_pct': None,
'open_date': ANY,
'open_date_hum': 'just now',
'open_order': '(limit buy rem=0.00000000)',
'open_rate': 1.099e-05,
'pair': 'ETH/BTC',
'stake_amount': 0.001,
'stop_loss': 0.0,
'stop_loss_pct': None,
'trade_id': 1}]
def test_api_version(botclient):
ftbot, client = botclient
rc = client.get("/version")
assert_response(rc)
assert rc.json == {"version": __version__}
def test_api_blacklist(botclient, mocker, ticker, fee, markets):
ftbot, client = botclient
rc = client.get("/blacklist")
assert_response(rc)
assert rc.json == {"blacklist": ["DOGE/BTC", "HOT/BTC"],
"length": 2,
"method": "StaticPairList"}
# Add ETH/BTC to blacklist
rc = client.post("/blacklist", data='{"blacklist": ["ETH/BTC"]}',
content_type='application/json')
assert_response(rc)
assert rc.json == {"blacklist": ["DOGE/BTC", "HOT/BTC", "ETH/BTC"],
"length": 3,
"method": "StaticPairList"}
def test_api_whitelist(botclient, mocker, ticker, fee, markets):
ftbot, client = botclient
rc = client.get("/whitelist")
assert_response(rc)
assert rc.json == {"whitelist": ['ETH/BTC', 'LTC/BTC', 'XRP/BTC', 'NEO/BTC'],
"length": 4,
"method": "StaticPairList"}