class WebMockTestCase(unittest.TestCase):
import time
- def market_args(self, debug=False, quiet=False):
- return type('Args', (object,), { "debug": debug, "quiet": quiet })()
+ def market_args(self, debug=False, quiet=False, report_path=None, **kwargs):
+ return main.configargparse.Namespace(report_path=report_path,
+ debug=debug, quiet=quiet, **kwargs)
def setUp(self):
super().setUp()
super().tearDown()
def test__init(self):
- with mock.patch("market.ccxt.poloniexE.session") as session:
+ with self.subTest("Nominal case"), \
+ mock.patch("market.ccxt.poloniexE.session") as session:
session.request.return_value = "response"
ccxt = market.ccxt.poloniexE()
ccxt._market = mock.Mock
ccxt._market.report.log_http_request.assert_called_with('GET', 'URL', 'data',
'headers', 'response')
+ with self.subTest("Raising"),\
+ mock.patch("market.ccxt.poloniexE.session") as session:
+ session.request.side_effect = market.ccxt.RequestException("Boo")
+
+ ccxt = market.ccxt.poloniexE()
+ ccxt._market = mock.Mock
+ ccxt._market.report = mock.Mock()
+
+ with self.assertRaises(market.ccxt.RequestException, msg="Boo") as cm:
+ ccxt.session.request("GET", "URL", data="data",
+ headers="headers")
+ ccxt._market.report.log_http_request.assert_called_with('GET', 'URL', 'data',
+ 'headers', cm.exception)
+
+
def test_nanoseconds(self):
with mock.patch.object(market.ccxt.time, "time") as time:
time.return_value = 123456.7890123456
with self.subTest(quiet=False):
m = market.Market(self.ccxt, self.market_args(quiet=False))
report_store.assert_called_with(m, verbose_print=True)
+ report_store().log_market.assert_called_once()
+ report_store.reset_mock()
with self.subTest(quiet=True):
m = market.Market(self.ccxt, self.market_args(quiet=True))
report_store.assert_called_with(m, verbose_print=False)
+ report_store().log_market.assert_called_once()
@mock.patch("market.ccxt")
def test_from_config(self, ccxt):
else:
time_mock.assert_called_with(sleep)
+ with self.subTest("disappearing order"), \
+ mock.patch("market.ReportStore"):
+ all_orders.reset_mock()
+ m = market.Market(self.ccxt, self.market_args())
+
+ order_mock1 = mock.Mock()
+ order_mock2 = mock.Mock()
+ all_orders.side_effect = [
+ [order_mock1, order_mock2],
+ [order_mock1, order_mock2],
+
+ [order_mock1, order_mock2],
+ [order_mock1, order_mock2],
+
+ []
+ ]
+
+ order_mock1.get_status.side_effect = ["open", "closed"]
+ order_mock2.get_status.side_effect = ["open", "error_disappeared"]
+
+ order_mock1.trade = mock.Mock()
+ trade_mock = mock.Mock()
+ order_mock2.trade = trade_mock
+
+ trade_mock.tick_actions_recreate.return_value = "tick1"
+
+ m.follow_orders()
+
+ trade_mock.tick_actions_recreate.assert_called_once_with(2)
+ trade_mock.prepare_order.assert_called_once_with(compute_value="tick1")
+ m.report.log_error.assert_called_once_with("follow_orders", message=mock.ANY)
+
@mock.patch.object(market.BalanceStore, "fetch_balances")
def test_move_balance(self, fetch_balances):
for debug in [True, False]:
def test_store_file_report(self):
file_open = mock.mock_open()
- m = market.Market(self.ccxt, self.market_args(), report_path="present", user_id=1)
+ m = market.Market(self.ccxt,
+ self.market_args(report_path="present"), user_id=1)
with self.subTest(file="present"),\
mock.patch("market.open", file_open),\
mock.patch.object(m, "report") as report,\
file_open().write.assert_any_call("Foo\nBar")
m.report.to_json.assert_called_once_with()
- m = market.Market(self.ccxt, self.market_args(), report_path="error", user_id=1)
+ m = market.Market(self.ccxt, self.market_args(report_path="error"), user_id=1)
with self.subTest(file="error"),\
mock.patch("market.open") as file_open,\
mock.patch.object(m, "report") as report,\
self.assertEqual(stdout_mock.getvalue(), "impossible to store report to database: Exception; Bouh\n")
def test_store_report(self):
- m = market.Market(self.ccxt, self.market_args(), user_id=1)
+ m = market.Market(self.ccxt, self.market_args(report_db=False), user_id=1)
with self.subTest(file=None, pg_config=None),\
mock.patch.object(m, "report") as report,\
mock.patch.object(m, "store_database_report") as db_report,\
db_report.assert_not_called()
report.reset_mock()
- m = market.Market(self.ccxt, self.market_args(), report_path="present", user_id=1)
+ m = market.Market(self.ccxt, self.market_args(report_db=False, report_path="present"), user_id=1)
with self.subTest(file="present", pg_config=None),\
mock.patch.object(m, "report") as report,\
mock.patch.object(m, "store_file_report") as file_report,\
db_report.assert_not_called()
report.reset_mock()
- m = market.Market(self.ccxt, self.market_args(), pg_config="present", user_id=1)
+ m = market.Market(self.ccxt, self.market_args(report_db=True, report_path="present"), user_id=1)
+ with self.subTest(file="present", pg_config=None, report_db=True),\
+ mock.patch.object(m, "report") as report,\
+ mock.patch.object(m, "store_file_report") as file_report,\
+ mock.patch.object(m, "store_database_report") as db_report,\
+ mock.patch.object(market, "datetime") as time_mock:
+
+ time_mock.now.return_value = datetime.datetime(2018, 2, 25)
+
+ m.store_report()
+
+ report.merge.assert_called_with(store.Portfolio.report)
+ file_report.assert_called_once_with(datetime.datetime(2018, 2, 25))
+ db_report.assert_not_called()
+
+ report.reset_mock()
+ m = market.Market(self.ccxt, self.market_args(report_db=True), pg_config="present", user_id=1)
with self.subTest(file=None, pg_config="present"),\
mock.patch.object(m, "report") as report,\
mock.patch.object(m, "store_file_report") as file_report,\
db_report.assert_called_once_with(datetime.datetime(2018, 2, 25))
report.reset_mock()
- m = market.Market(self.ccxt, self.market_args(),
- pg_config="pg_config", report_path="present", user_id=1)
+ m = market.Market(self.ccxt, self.market_args(report_db=True, report_path="present"),
+ pg_config="pg_config", user_id=1)
with self.subTest(file="present", pg_config="present"),\
mock.patch.object(m, "report") as report,\
mock.patch.object(m, "store_file_report") as file_report,\
D("125"), "FOO", "long", self.m,
trade, close_if_possible=False)
+ def test_tick_actions_recreate(self):
+ value_from = portfolio.Amount("BTC", "0.5")
+ value_from.linked_to = portfolio.Amount("ETH", "10.0")
+ value_to = portfolio.Amount("BTC", "1.0")
+ trade = portfolio.Trade(value_from, value_to, "ETH", self.m)
+
+ self.assertEqual("average", trade.tick_actions_recreate(0))
+ self.assertEqual("foo", trade.tick_actions_recreate(0, default="foo"))
+ self.assertEqual("average", trade.tick_actions_recreate(1))
+ self.assertEqual(trade.tick_actions[2][1], trade.tick_actions_recreate(2))
+ self.assertEqual(trade.tick_actions[2][1], trade.tick_actions_recreate(3))
+ self.assertEqual(trade.tick_actions[5][1], trade.tick_actions_recreate(5))
+ self.assertEqual(trade.tick_actions[5][1], trade.tick_actions_recreate(6))
+ self.assertEqual("default", trade.tick_actions_recreate(7))
+ self.assertEqual("default", trade.tick_actions_recreate(8))
@mock.patch.object(portfolio.Trade, "prepare_order")
def test_update_order(self, prepare_order):
self.m.ccxt.privatePostReturnOrderTrades.return_value = [
{
"tradeID": 42, "type": "buy", "fee": "0.0015",
- "date": "2017-12-30 12:00:12", "rate": "0.1",
+ "date": "2017-12-30 13:00:12", "rate": "0.1",
"amount": "3", "total": "0.3"
},
{
"tradeID": 43, "type": "buy", "fee": "0.0015",
- "date": "2017-12-30 13:00:12", "rate": "0.2",
+ "date": "2017-12-30 12:00:12", "rate": "0.2",
"amount": "2", "total": "0.4"
}
]
self.m.ccxt.privatePostReturnOrderTrades.assert_called_with({"orderNumber": 12})
self.assertEqual(2, len(order.mouvements))
- self.assertEqual(42, order.mouvements[0].id)
- self.assertEqual(43, order.mouvements[1].id)
+ self.assertEqual(43, order.mouvements[0].id)
+ self.assertEqual(42, order.mouvements[1].id)
self.m.ccxt.privatePostReturnOrderTrades.side_effect = portfolio.ExchangeError
order = portfolio.Order("buy", portfolio.Amount("ETH", 10),
self.m.report.log_debug_action.assert_called_once()
@mock.patch.object(portfolio.Order, "fetch_mouvements")
+ @mock.patch.object(portfolio.Order, "mark_disappeared_order")
@mock.patch.object(portfolio.Order, "mark_finished_order")
- def test_fetch(self, mark_finished_order, fetch_mouvements):
+ def test_fetch(self, mark_finished_order, mark_disappeared_order, fetch_mouvements):
order = portfolio.Order("buy", portfolio.Amount("ETH", 10),
D("0.1"), "BTC", "long", self.m, "trade")
order.id = 45
self.m.report.log_debug_action.reset_mock()
self.m.ccxt.fetch_order.assert_not_called()
mark_finished_order.assert_not_called()
+ mark_disappeared_order.assert_not_called()
fetch_mouvements.assert_not_called()
with self.subTest(debug=False):
self.assertEqual(1, len(order.results))
self.m.report.log_debug_action.assert_not_called()
mark_finished_order.assert_called_once()
+ mark_disappeared_order.assert_called_once()
mark_finished_order.reset_mock()
with self.subTest(missing_order=True):
self.assertEqual("closed_unknown", order.status)
mark_finished_order.assert_called_once()
+ def test_mark_disappeared_order(self):
+ with self.subTest("Open order"):
+ order = portfolio.Order("buy", portfolio.Amount("ETH", 10),
+ D("0.1"), "BTC", "long", self.m, "trade")
+ order.id = 45
+ order.mouvements.append(portfolio.Mouvement("XRP", "BTC", {
+ "tradeID":21336541,
+ "currencyPair":"BTC_XRP",
+ "type":"sell",
+ "rate":"0.00007013",
+ "amount":"0.00000222",
+ "total":"0.00000000",
+ "fee":"0.00150000",
+ "date":"2018-04-02 00:09:13"
+ }))
+ order.mark_disappeared_order()
+ self.assertEqual("pending", order.status)
+
+ with self.subTest("Non-zero amount"):
+ order = portfolio.Order("buy", portfolio.Amount("ETH", 10),
+ D("0.1"), "BTC", "long", self.m, "trade")
+ order.id = 45
+ order.status = "closed"
+ order.mouvements.append(portfolio.Mouvement("XRP", "BTC", {
+ "tradeID":21336541,
+ "currencyPair":"BTC_XRP",
+ "type":"sell",
+ "rate":"0.00007013",
+ "amount":"0.00000222",
+ "total":"0.00000010",
+ "fee":"0.00150000",
+ "date":"2018-04-02 00:09:13"
+ }))
+ order.mark_disappeared_order()
+ self.assertEqual("closed", order.status)
+
+ with self.subTest("Other mouvements"):
+ order = portfolio.Order("buy", portfolio.Amount("ETH", 10),
+ D("0.1"), "BTC", "long", self.m, "trade")
+ order.id = 45
+ order.status = "closed"
+ order.mouvements.append(portfolio.Mouvement("XRP", "BTC", {
+ "tradeID":21336541,
+ "currencyPair":"BTC_XRP",
+ "type":"sell",
+ "rate":"0.00007013",
+ "amount":"0.00000222",
+ "total":"0.00000001",
+ "fee":"0.00150000",
+ "date":"2018-04-02 00:09:13"
+ }))
+ order.mouvements.append(portfolio.Mouvement("XRP", "BTC", {
+ "tradeID":21336541,
+ "currencyPair":"BTC_XRP",
+ "type":"sell",
+ "rate":"0.00007013",
+ "amount":"0.00000222",
+ "total":"0.00000000",
+ "fee":"0.00150000",
+ "date":"2018-04-02 00:09:13"
+ }))
+ order.mark_disappeared_order()
+ self.assertEqual("error_disappeared", order.status)
+
+ with self.subTest("Order disappeared"):
+ order = portfolio.Order("buy", portfolio.Amount("ETH", 10),
+ D("0.1"), "BTC", "long", self.m, "trade")
+ order.id = 45
+ order.status = "closed"
+ order.mouvements.append(portfolio.Mouvement("XRP", "BTC", {
+ "tradeID":21336541,
+ "currencyPair":"BTC_XRP",
+ "type":"sell",
+ "rate":"0.00007013",
+ "amount":"0.00000222",
+ "total":"0.00000000",
+ "fee":"0.00150000",
+ "date":"2018-04-02 00:09:13"
+ }))
+ order.mark_disappeared_order()
+ self.assertEqual("error_disappeared", order.status)
+
@mock.patch.object(portfolio.Order, "fetch")
def test_get_status(self, fetch):
with self.subTest(debug=True):
'response': 'Hey'
})
+ add_log.reset_mock()
+ report_store.log_http_request("method", "url", "body",
+ "headers", ValueError("Foo"))
+ add_log.assert_called_once_with({
+ 'type': 'http_request',
+ 'method': 'method',
+ 'url': 'url',
+ 'body': 'body',
+ 'headers': 'headers',
+ 'status': -1,
+ 'response': None,
+ 'error': 'ValueError',
+ 'error_message': 'Foo',
+ })
+
+ @mock.patch.object(market.ReportStore, "add_log")
+ def test_log_market(self, add_log):
+ report_store = market.ReportStore(self.m)
+
+ report_store.log_market(self.market_args(debug=True, quiet=False), 4, 1)
+ add_log.assert_called_once_with({
+ "type": "market",
+ "commit": "$Format:%H$",
+ "args": { "report_path": None, "debug": True, "quiet": False },
+ "user_id": 4,
+ "market_id": 1,
+ })
+
@mock.patch.object(market.ReportStore, "print_log")
@mock.patch.object(market.ReportStore, "add_log")
def test_log_error(self, add_log, print_log):
args_mock.after = "after"
self.assertEqual("", stdout_mock.getvalue())
- main.process("config", 3, 1, args_mock, "report_path", "pg_config")
+ main.process("config", 3, 1, args_mock, "pg_config")
market_mock.from_config.assert_has_calls([
- mock.call("config", args_mock, pg_config="pg_config", market_id=3, user_id=1, report_path="report_path"),
+ mock.call("config", args_mock, pg_config="pg_config", market_id=3, user_id=1),
mock.call().process("action", before="before", after="after"),
])
with self.subTest(exception=True):
market_mock.from_config.side_effect = Exception("boo")
- main.process(3, "config", 1, "report_path", args_mock, "pg_config")
+ main.process(3, "config", 1, args_mock, "pg_config")
self.assertEqual("Exception: boo\n", stdout_mock.getvalue())
def test_main(self):
args_mock = mock.Mock()
args_mock.parallel = False
- args_mock.config = "config"
args_mock.user = "user"
parse_args.return_value = args_mock
- parse_config.return_value = ["pg_config", "report_path"]
+ parse_config.return_value = "pg_config"
fetch_markets.return_value = [[3, "config1", 1], [1, "config2", 2]]
main.main(["Foo", "Bar"])
parse_args.assert_called_with(["Foo", "Bar"])
- parse_config.assert_called_with("config")
+ parse_config.assert_called_with(args_mock)
fetch_markets.assert_called_with("pg_config", "user")
self.assertEqual(2, process.call_count)
process.assert_has_calls([
- mock.call("config1", 3, 1, args_mock, "report_path", "pg_config"),
- mock.call("config2", 1, 2, args_mock, "report_path", "pg_config"),
+ mock.call("config1", 3, 1, args_mock, "pg_config"),
+ mock.call("config2", 1, 2, args_mock, "pg_config"),
])
with self.subTest(parallel=True):
with mock.patch("main.parse_args") as parse_args,\
args_mock = mock.Mock()
args_mock.parallel = True
- args_mock.config = "config"
args_mock.user = "user"
parse_args.return_value = args_mock
- parse_config.return_value = ["pg_config", "report_path"]
+ parse_config.return_value = "pg_config"
fetch_markets.return_value = [[3, "config1", 1], [1, "config2", 2]]
main.main(["Foo", "Bar"])
parse_args.assert_called_with(["Foo", "Bar"])
- parse_config.assert_called_with("config")
+ parse_config.assert_called_with(args_mock)
fetch_markets.assert_called_with("pg_config", "user")
start.assert_called_once_with()
self.assertEqual(2, process.call_count)
process.assert_has_calls([
mock.call.__bool__(),
- mock.call("config1", 3, 1, args_mock, "report_path", "pg_config"),
+ mock.call("config1", 3, 1, args_mock, "pg_config"),
mock.call.__bool__(),
- mock.call("config2", 1, 2, args_mock, "report_path", "pg_config"),
+ mock.call("config2", 1, 2, args_mock, "pg_config"),
])
@mock.patch.object(main.sys, "exit")
- @mock.patch("main.configparser")
@mock.patch("main.os")
- def test_parse_config(self, os, configparser, exit):
- with self.subTest(pg_config=True, report_path=None):
- config_mock = mock.MagicMock()
- configparser.ConfigParser.return_value = config_mock
- def config(element):
- return element == "postgresql"
-
- config_mock.__contains__.side_effect = config
- config_mock.__getitem__.return_value = "pg_config"
-
- result = main.parse_config("configfile")
-
- config_mock.read.assert_called_with("configfile")
-
- self.assertEqual(["pg_config", None], result)
-
- with self.subTest(pg_config=True, report_path="present"):
- config_mock = mock.MagicMock()
- configparser.ConfigParser.return_value = config_mock
+ def test_parse_config(self, os, exit):
+ with self.subTest(report_path=None):
+ args = main.configargparse.Namespace(**{
+ "db_host": "host",
+ "db_port": "port",
+ "db_user": "user",
+ "db_password": "password",
+ "db_database": "database",
+ "report_path": None,
+ })
- config_mock.__contains__.return_value = True
- config_mock.__getitem__.side_effect = [
- {"report_path": "report_path"},
- {"report_path": "report_path"},
- "pg_config",
- ]
+ result = main.parse_config(args)
+ self.assertEqual({ "host": "host", "port": "port", "user":
+ "user", "password": "password", "database": "database"
+ }, result)
+ with self.assertRaises(AttributeError):
+ args.db_password
+
+ with self.subTest(report_path="present"):
+ args = main.configargparse.Namespace(**{
+ "db_host": "host",
+ "db_port": "port",
+ "db_user": "user",
+ "db_password": "password",
+ "db_database": "database",
+ "report_path": "report_path",
+ })
os.path.exists.return_value = False
- result = main.parse_config("configfile")
- config_mock.read.assert_called_with("configfile")
- self.assertEqual(["pg_config", "report_path"], result)
+ result = main.parse_config(args)
+
os.path.exists.assert_called_once_with("report_path")
os.makedirs.assert_called_once_with("report_path")
- with self.subTest(pg_config=False),\
- mock.patch('sys.stdout', new_callable=StringIO) as stdout_mock:
- config_mock = mock.MagicMock()
- configparser.ConfigParser.return_value = config_mock
- result = main.parse_config("configfile")
-
- config_mock.read.assert_called_with("configfile")
- exit.assert_called_once_with(1)
- self.assertEqual("no configuration for postgresql in config file\n", stdout_mock.getvalue())
-
- @mock.patch.object(main.sys, "exit")
- def test_parse_args(self, exit):
+ def test_parse_args(self):
with self.subTest(config="config.ini"):
args = main.parse_args([])
self.assertEqual("config.ini", args.config)
self.assertTrue(args.after)
self.assertTrue(args.debug)
- exit.assert_not_called()
-
- with self.subTest(config="inexistant"),\
- mock.patch('sys.stdout', new_callable=StringIO) as stdout_mock:
+ with self.subTest(config="inexistant"), \
+ self.assertRaises(SystemExit), \
+ mock.patch('sys.stderr', new_callable=StringIO) as stdout_mock:
args = main.parse_args(["--config", "foo.bar"])
- exit.assert_called_once_with(1)
- self.assertEqual("no config file found, exiting\n", stdout_mock.getvalue())
@mock.patch.object(main, "psycopg2")
def test_fetch_markets(self, psycopg2):