From 6ce6018bb7c91aed17a2b323cb9683f55dfed05a Mon Sep 17 00:00:00 2001 From: gcarq Date: Tue, 7 Nov 2017 22:27:44 +0100 Subject: [PATCH] add more tests --- freqtrade/tests/conftest.py | 2 +- freqtrade/tests/test_main.py | 180 +++++++++++++++++++++++++++---- freqtrade/tests/test_telegram.py | 39 ++++++- 3 files changed, 194 insertions(+), 27 deletions(-) diff --git a/freqtrade/tests/conftest.py b/freqtrade/tests/conftest.py index 64bdd8d9f..25d77d688 100644 --- a/freqtrade/tests/conftest.py +++ b/freqtrade/tests/conftest.py @@ -14,7 +14,7 @@ from freqtrade.misc import CONF_SCHEMA def default_conf(): """ Returns validated configuration suitable for most tests """ configuration = { - "max_open_trades": 3, + "max_open_trades": 1, "stake_currency": "BTC", "stake_amount": 0.05, "dry_run": True, diff --git a/freqtrade/tests/test_main.py b/freqtrade/tests/test_main.py index 15754cbc9..61ff8c1fe 100644 --- a/freqtrade/tests/test_main.py +++ b/freqtrade/tests/test_main.py @@ -1,16 +1,101 @@ # pragma pylint: disable=missing-docstring import copy -from unittest.mock import MagicMock, call +from unittest.mock import MagicMock + +import pytest +import requests from freqtrade.exchange import Exchanges from freqtrade.main import create_trade, handle_trade, close_trade_if_fulfilled, init, \ - get_target_bid + get_target_bid, _process +from freqtrade.misc import get_state, State from freqtrade.persistence import Trade +def test_process_trade_creation(default_conf, ticker, mocker): + mocker.patch.dict('freqtrade.main._CONF', default_conf) + mocker.patch.multiple('freqtrade.main.telegram', init=MagicMock(), send_msg=MagicMock()) + mocker.patch('freqtrade.main.get_buy_signal', side_effect=lambda _: True) + mocker.patch.multiple('freqtrade.main.exchange', + validate_pairs=MagicMock(), + get_ticker=ticker, + buy=MagicMock(return_value='mocked_limit_buy')) + init(default_conf, 'sqlite://') + + trades = Trade.query.filter(Trade.is_open.is_(True)).all() + assert len(trades) == 0 + + result = _process() + assert result is True + + trades = Trade.query.filter(Trade.is_open.is_(True)).all() + assert len(trades) == 1 + trade = trades[0] + assert trade is not None + assert trade.stake_amount == default_conf['stake_amount'] + assert trade.is_open + assert trade.open_date is not None + assert trade.exchange == Exchanges.BITTREX.name + assert trade.open_rate == 0.072661 + assert trade.amount == 0.6864067381401302 + + +def test_process_exchange_failures(default_conf, ticker, mocker): + mocker.patch.dict('freqtrade.main._CONF', default_conf) + mocker.patch.multiple('freqtrade.main.telegram', init=MagicMock(), send_msg=MagicMock()) + mocker.patch('freqtrade.main.get_buy_signal', side_effect=lambda _: True) + sleep_mock = mocker.patch('time.sleep', side_effect=lambda _: None) + mocker.patch.multiple('freqtrade.main.exchange', + validate_pairs=MagicMock(), + get_ticker=ticker, + buy=MagicMock(side_effect=requests.exceptions.RequestException)) + init(default_conf, 'sqlite://') + result = _process() + assert result is False + assert sleep_mock.has_calls() + + +def test_process_runtime_error(default_conf, ticker, mocker): + msg_mock = MagicMock() + mocker.patch.dict('freqtrade.main._CONF', default_conf) + mocker.patch.multiple('freqtrade.main.telegram', init=MagicMock(), send_msg=msg_mock) + mocker.patch('freqtrade.main.get_buy_signal', side_effect=lambda _: True) + mocker.patch.multiple('freqtrade.main.exchange', + validate_pairs=MagicMock(), + get_ticker=ticker, + buy=MagicMock(side_effect=RuntimeError)) + init(default_conf, 'sqlite://') + assert get_state() == State.RUNNING + + result = _process() + assert result is False + assert get_state() == State.STOPPED + assert 'RuntimeError' in msg_mock.call_args_list[-1][0][0] + + +def test_process_trade_handling(default_conf, ticker, limit_buy_order, mocker): + mocker.patch.dict('freqtrade.main._CONF', default_conf) + mocker.patch.multiple('freqtrade.main.telegram', init=MagicMock(), send_msg=MagicMock()) + mocker.patch('freqtrade.main.get_buy_signal', side_effect=lambda _: True) + mocker.patch.multiple('freqtrade.main.exchange', + validate_pairs=MagicMock(), + get_ticker=ticker, + buy=MagicMock(return_value='mocked_limit_buy'), + get_order=MagicMock(return_value=limit_buy_order)), + init(default_conf, 'sqlite://') + + assert len(Trade.query.filter(Trade.is_open.is_(True)).all()) == 0 + result = _process() + assert result is True + assert len(Trade.query.filter(Trade.is_open.is_(True)).all()) == 1 + + result = _process() + assert result is False + + def test_create_trade(default_conf, ticker, limit_buy_order, mocker): mocker.patch.dict('freqtrade.main._CONF', default_conf) - buy_signal = mocker.patch('freqtrade.main.get_buy_signal', side_effect=lambda _: True) + mocker.patch('freqtrade.main.get_buy_signal', side_effect=lambda _: True) mocker.patch.multiple('freqtrade.main.telegram', init=MagicMock(), send_msg=MagicMock()) mocker.patch.multiple('freqtrade.main.exchange', validate_pairs=MagicMock(), @@ -20,31 +105,56 @@ def test_create_trade(default_conf, ticker, limit_buy_order, mocker): whitelist = copy.deepcopy(default_conf['exchange']['pair_whitelist']) init(default_conf, 'sqlite://') - for _ in ['BTC_ETH', 'BTC_TKN', 'BTC_TRST', 'BTC_SWT']: - trade = create_trade(15.0) - Trade.session.add(trade) - Trade.session.flush() - assert trade is not None - assert trade.stake_amount == 15.0 - assert trade.is_open - assert trade.open_date is not None - assert trade.exchange == Exchanges.BITTREX.name + trade = create_trade(15.0) + Trade.session.add(trade) + Trade.session.flush() + assert trade is not None + assert trade.stake_amount == 15.0 + assert trade.is_open + assert trade.open_date is not None + assert trade.exchange == Exchanges.BITTREX.name - # Simulate fulfilled LIMIT_BUY order for trade - trade.update(limit_buy_order) + # Simulate fulfilled LIMIT_BUY order for trade + trade.update(limit_buy_order) - assert trade.open_rate == 0.07256061 - assert trade.amount == 206.43811673387373 + assert trade.open_rate == 0.07256061 + assert trade.amount == 206.43811673387373 - assert whitelist == default_conf['exchange']['pair_whitelist'] - - buy_signal.assert_has_calls( - [call('BTC_ETH'), call('BTC_TKN'), call('BTC_TRST'), call('BTC_SWT')] - ) + assert whitelist == default_conf['exchange']['pair_whitelist'] -def test_handle_trade(default_conf, limit_sell_order, mocker): +def test_create_trade_no_stake_amount(default_conf, ticker, mocker): mocker.patch.dict('freqtrade.main._CONF', default_conf) + mocker.patch('freqtrade.main.get_buy_signal', side_effect=lambda _: True) + mocker.patch.multiple('freqtrade.main.telegram', init=MagicMock(), send_msg=MagicMock()) + mocker.patch.multiple('freqtrade.main.exchange', + validate_pairs=MagicMock(), + get_ticker=ticker, + buy=MagicMock(return_value='mocked_limit_buy'), + get_balance=MagicMock(return_value=default_conf['stake_amount'] * 0.5)) + with pytest.raises(ValueError, match=r'.*stake amount.*'): + create_trade(default_conf['stake_amount']) + + +def test_create_trade_no_pairs(default_conf, ticker, mocker): + mocker.patch.dict('freqtrade.main._CONF', default_conf) + mocker.patch('freqtrade.main.get_buy_signal', side_effect=lambda _: True) + mocker.patch.multiple('freqtrade.main.telegram', init=MagicMock(), send_msg=MagicMock()) + mocker.patch.multiple('freqtrade.main.exchange', + validate_pairs=MagicMock(), + get_ticker=ticker, + buy=MagicMock(return_value='mocked_limit_buy')) + + with pytest.raises(ValueError, match=r'.*No pair in whitelist.*'): + conf = copy.deepcopy(default_conf) + conf['exchange']['pair_whitelist'] = [] + mocker.patch.dict('freqtrade.main._CONF', conf) + create_trade(default_conf['stake_amount']) + + +def test_handle_trade(default_conf, limit_buy_order, limit_sell_order, mocker): + mocker.patch.dict('freqtrade.main._CONF', default_conf) + mocker.patch('freqtrade.main.get_buy_signal', side_effect=lambda _: True) mocker.patch.multiple('freqtrade.main.telegram', init=MagicMock(), send_msg=MagicMock()) mocker.patch.multiple('freqtrade.main.exchange', validate_pairs=MagicMock(), @@ -53,12 +163,19 @@ def test_handle_trade(default_conf, limit_sell_order, mocker): 'ask': 0.172661, 'last': 0.17256061 }), + buy=MagicMock(return_value='mocked_limit_buy'), sell=MagicMock(return_value='mocked_limit_sell')) + init(default_conf, 'sqlite://') + trade = create_trade(15.0) + trade.update(limit_buy_order) + Trade.session.add(trade) + Trade.session.flush() trade = Trade.query.filter(Trade.is_open.is_(True)).first() assert trade handle_trade(trade) assert trade.open_order_id == 'mocked_limit_sell' + assert close_trade_if_fulfilled(trade) is False # Simulate fulfilled LIMIT_SELL order for trade trade.update(limit_sell_order) @@ -68,8 +185,23 @@ def test_handle_trade(default_conf, limit_sell_order, mocker): assert trade.close_date is not None -def test_close_trade(default_conf, mocker): +def test_close_trade(default_conf, ticker, limit_buy_order, limit_sell_order, mocker): mocker.patch.dict('freqtrade.main._CONF', default_conf) + mocker.patch('freqtrade.main.get_buy_signal', side_effect=lambda _: True) + mocker.patch.multiple('freqtrade.main.telegram', init=MagicMock(), send_msg=MagicMock()) + mocker.patch.multiple('freqtrade.main.exchange', + validate_pairs=MagicMock(), + get_ticker=ticker, + buy=MagicMock(return_value='mocked_limit_buy')) + + # Create trade and sell it + init(default_conf, 'sqlite://') + trade = create_trade(15.0) + trade.update(limit_buy_order) + trade.update(limit_sell_order) + + Trade.session.add(trade) + Trade.session.flush() trade = Trade.query.filter(Trade.is_open.is_(True)).first() assert trade @@ -79,6 +211,8 @@ def test_close_trade(default_conf, mocker): closed = close_trade_if_fulfilled(trade) assert closed assert not trade.is_open + with pytest.raises(ValueError, match=r'.*closed trade.*'): + handle_trade(trade) def test_balance_fully_ask_side(mocker): diff --git a/freqtrade/tests/test_telegram.py b/freqtrade/tests/test_telegram.py index f9e91a0c6..3e946dc25 100644 --- a/freqtrade/tests/test_telegram.py +++ b/freqtrade/tests/test_telegram.py @@ -1,22 +1,55 @@ # pragma pylint: disable=missing-docstring, too-many-arguments, too-many-ancestors import re from datetime import datetime +from random import randint from unittest.mock import MagicMock -from telegram import Bot +from telegram import Bot, Update, Message, Chat from freqtrade.main import init, create_trade from freqtrade.misc import update_state, State, get_state from freqtrade.persistence import Trade from freqtrade.rpc.telegram import ( - _status, _status_table, _profit, _forcesell, _performance, _count, _start, _stop, _balance -) + _status, _status_table, _profit, _forcesell, _performance, _count, _start, _stop, _balance, + authorized_only) class MagicBot(MagicMock, Bot): pass +def test_authorized_only(default_conf, mocker): + mocker.patch.dict('freqtrade.rpc.telegram._CONF', default_conf) + + chat = Chat(0, 0) + update = Update(randint(1, 100)) + update.message = Message(randint(1, 100), 0, datetime.utcnow(), chat) + state = {'called': False} + + @authorized_only + def dummy_handler(*args, **kwargs) -> None: + state['called'] = True + + dummy_handler(MagicMock(), update) + assert state['called'] is True + + +def test_authorized_only_unauthorized(default_conf, mocker): + mocker.patch.dict('freqtrade.rpc.telegram._CONF', default_conf) + + chat = Chat(0xdeadbeef, 0) + update = Update(randint(1, 100)) + update.message = Message(randint(1, 100), 0, datetime.utcnow(), chat) + state = {'called': False} + + @authorized_only + def dummy_handler(*args, **kwargs) -> None: + state['called'] = True + + dummy_handler(MagicMock(), update) + assert state['called'] is False + + def test_status_handle(default_conf, update, ticker, mocker): mocker.patch.dict('freqtrade.main._CONF', default_conf) mocker.patch('freqtrade.main.get_buy_signal', side_effect=lambda _: True)