X-Git-Url: https://git.immae.eu/?a=blobdiff_plain;f=test.py;h=cbce357b246fb9761e59d152338c07b4c66bcf18;hb=e6015816224f8f405e9b1c9557f22e73b21246e8;hp=7b17f9ebc54cd3b68c3a777157a61a5ed60ccf83;hpb=7a676e2b0db5a168f08b419701cb71c28d7c76ee;p=perso%2FImmae%2FProjets%2FCryptomonnaies%2FCryptoportfolio%2FTrader.git diff --git a/test.py b/test.py index 7b17f9e..cbce357 100644 --- a/test.py +++ b/test.py @@ -23,8 +23,9 @@ for test_type in limits: 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() @@ -71,7 +72,8 @@ class poloniexETest(unittest.TestCase): 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 @@ -82,6 +84,21 @@ class poloniexETest(unittest.TestCase): 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 @@ -1181,9 +1198,12 @@ class MarketTest(WebMockTestCase): 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): @@ -1403,6 +1423,38 @@ class MarketTest(WebMockTestCase): 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]: @@ -1581,7 +1633,8 @@ class MarketTest(WebMockTestCase): 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,\ @@ -1598,7 +1651,7 @@ class MarketTest(WebMockTestCase): 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,\ @@ -1646,7 +1699,7 @@ class MarketTest(WebMockTestCase): 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,\ @@ -1658,7 +1711,7 @@ class MarketTest(WebMockTestCase): 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,\ @@ -1674,7 +1727,23 @@ class MarketTest(WebMockTestCase): 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,\ @@ -1690,8 +1759,8 @@ class MarketTest(WebMockTestCase): 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,\ @@ -2406,27 +2475,6 @@ class TradeTest(WebMockTestCase): order1.filled_amount.assert_called_with(in_base_currency=True) order2.filled_amount.assert_called_with(in_base_currency=True) - def test_reopen_same_order(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) - order = portfolio.Order("buy", portfolio.Amount("ETH", 10), - D("0.1"), "BTC", "long", self.m, "trade") - with mock.patch("portfolio.Order.run") as run: - new_order = trade.reopen_same_order(order) - self.assertEqual("buy", new_order.action) - self.assertEqual(portfolio.Amount("ETH", 10), new_order.amount) - self.assertEqual(D("0.1"), new_order.rate) - self.assertEqual("BTC", new_order.base_currency) - self.assertEqual("long", new_order.trade_type) - self.assertEqual(self.m, new_order.market) - self.assertEqual(False, new_order.close_if_possible) - self.assertEqual(trade, new_order.trade) - run.assert_called_once() - self.assertEqual(1, len(trade.orders)) - self.assertEqual(new_order, trade.orders[0]) - @mock.patch.object(portfolio.Computation, "compute_value") @mock.patch.object(portfolio.Trade, "filled_amount") @mock.patch.object(portfolio, "Order") @@ -2582,6 +2630,21 @@ class TradeTest(WebMockTestCase): 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): @@ -2984,12 +3047,12 @@ class OrderTest(WebMockTestCase): 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" } ] @@ -3002,8 +3065,8 @@ class OrderTest(WebMockTestCase): 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), @@ -3059,9 +3122,9 @@ class OrderTest(WebMockTestCase): self.m.report.log_debug_action.assert_called_once() @mock.patch.object(portfolio.Order, "fetch_mouvements") - @mock.patch.object(portfolio.Order, "fix_disappeared_order") + @mock.patch.object(portfolio.Order, "mark_disappeared_order") @mock.patch.object(portfolio.Order, "mark_finished_order") - def test_fetch(self, mark_finished_order, fix_disappeared_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 @@ -3072,7 +3135,7 @@ class OrderTest(WebMockTestCase): self.m.report.log_debug_action.reset_mock() self.m.ccxt.fetch_order.assert_not_called() mark_finished_order.assert_not_called() - fix_disappeared_order.assert_not_called() + mark_disappeared_order.assert_not_called() fetch_mouvements.assert_not_called() with self.subTest(debug=False): @@ -3090,7 +3153,7 @@ class OrderTest(WebMockTestCase): self.assertEqual(1, len(order.results)) self.m.report.log_debug_action.assert_not_called() mark_finished_order.assert_called_once() - fix_disappeared_order.assert_called_once() + mark_disappeared_order.assert_called_once() mark_finished_order.reset_mock() with self.subTest(missing_order=True): @@ -3101,7 +3164,7 @@ class OrderTest(WebMockTestCase): self.assertEqual("closed_unknown", order.status) mark_finished_order.assert_called_once() - def test_fix_disappeared_order(self): + 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") @@ -3116,9 +3179,8 @@ class OrderTest(WebMockTestCase): "fee":"0.00150000", "date":"2018-04-02 00:09:13" })) - with mock.patch.object(order, "trade") as trade: - order.fix_disappeared_order() - trade.reopen_same_order.assert_not_called() + order.mark_disappeared_order() + self.assertEqual("pending", order.status) with self.subTest("Non-zero amount"): order = portfolio.Order("buy", portfolio.Amount("ETH", 10), @@ -3135,10 +3197,8 @@ class OrderTest(WebMockTestCase): "fee":"0.00150000", "date":"2018-04-02 00:09:13" })) - with mock.patch.object(order, "trade") as trade: - order.fix_disappeared_order() - self.assertEqual("closed", order.status) - trade.reopen_same_order.assert_not_called() + order.mark_disappeared_order() + self.assertEqual("closed", order.status) with self.subTest("Other mouvements"): order = portfolio.Order("buy", portfolio.Amount("ETH", 10), @@ -3165,10 +3225,8 @@ class OrderTest(WebMockTestCase): "fee":"0.00150000", "date":"2018-04-02 00:09:13" })) - with mock.patch.object(order, "trade") as trade: - order.fix_disappeared_order() - self.assertEqual("closed", order.status) - trade.reopen_same_order.assert_not_called() + order.mark_disappeared_order() + self.assertEqual("error_disappeared", order.status) with self.subTest("Order disappeared"): order = portfolio.Order("buy", portfolio.Amount("ETH", 10), @@ -3185,13 +3243,8 @@ class OrderTest(WebMockTestCase): "fee":"0.00150000", "date":"2018-04-02 00:09:13" })) - with mock.patch.object(order, "trade") as trade: - trade.reopen_same_order.return_value = "New order" - order.fix_disappeared_order() - self.assertEqual("error_disappeared", order.status) - trade.reopen_same_order.assert_called_once_with(order) - self.m.report.log_error.assert_called_once_with('fetch', - message='Order Order(buy long 10.00000000 ETH at 0.1 BTC [error_disappeared]) disappeared, recreating it as New order') + order.mark_disappeared_order() + self.assertEqual("error_disappeared", order.status) @mock.patch.object(portfolio.Order, "fetch") def test_get_status(self, fetch): @@ -4330,6 +4383,34 @@ class ReportStoreTest(WebMockTestCase): '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): @@ -4504,22 +4585,28 @@ class MainTest(WebMockTestCase): def test_get_user_market(self): with mock.patch("main.fetch_markets") as main_fetch_markets,\ + mock.patch("main.parse_args") as main_parse_args,\ mock.patch("main.parse_config") as main_parse_config: with self.subTest(debug=False): - main_parse_config.return_value = ["pg_config", "report_path"] + main_parse_args.return_value = self.market_args() + main_parse_config.return_value = "pg_config" main_fetch_markets.return_value = [(1, {"key": "market_config"}, 3)] m = main.get_user_market("config_path.ini", 1) self.assertIsInstance(m, market.Market) self.assertFalse(m.debug) + main_parse_args.assert_called_once_with(["--config", "config_path.ini"]) + main_parse_args.reset_mock() with self.subTest(debug=True): - main_parse_config.return_value = ["pg_config", "report_path"] + main_parse_args.return_value = self.market_args(debug=True) + main_parse_config.return_value = "pg_config" main_fetch_markets.return_value = [(1, {"key": "market_config"}, 3)] m = main.get_user_market("config_path.ini", 1, debug=True) self.assertIsInstance(m, market.Market) self.assertTrue(m.debug) + main_parse_args.assert_called_once_with(["--config", "config_path.ini", "--debug"]) def test_process(self): with mock.patch("market.Market") as market_mock,\ @@ -4534,16 +4621,16 @@ class MainTest(WebMockTestCase): 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): @@ -4555,24 +4642,23 @@ class MainTest(WebMockTestCase): 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,\ @@ -4583,79 +4669,66 @@ class MainTest(WebMockTestCase): 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) @@ -4668,13 +4741,10 @@ class MainTest(WebMockTestCase): 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):