from .helper import * import requests import datetime import threading import market, portfolio, store @unittest.skipUnless("unit" in limits, "Unit skipped") class NoopLockTest(unittest.TestCase): def test_with(self): noop_lock = store.NoopLock() with noop_lock: self.assertTrue(True) @unittest.skipUnless("unit" in limits, "Unit skipped") class LockedVarTest(unittest.TestCase): def test_values(self): locked_var = store.LockedVar("Foo") self.assertIsInstance(locked_var.lock, store.NoopLock) self.assertEqual("Foo", locked_var.val) def test_get(self): with self.subTest(desc="Normal case"): locked_var = store.LockedVar("Foo") self.assertEqual("Foo", locked_var.get()) with self.subTest(desc="Dict"): locked_var = store.LockedVar({"foo": "bar"}) self.assertEqual({"foo": "bar"}, locked_var.get()) self.assertEqual("bar", locked_var.get("foo")) self.assertIsNone(locked_var.get("other")) def test_set(self): locked_var = store.LockedVar("Foo") locked_var.set("Bar") self.assertEqual("Bar", locked_var.get()) def test__getattr(self): dummy = type('Dummy', (object,), {})() dummy.attribute = "Hey" locked_var = store.LockedVar(dummy) self.assertEqual("Hey", locked_var.attribute) with self.assertRaises(AttributeError): locked_var.other def test_start_lock(self): locked_var = store.LockedVar("Foo") locked_var.start_lock() self.assertEqual("lock", locked_var.lock.__class__.__name__) thread1 = threading.Thread(target=locked_var.set, args=["Bar1"]) thread2 = threading.Thread(target=locked_var.set, args=["Bar2"]) thread3 = threading.Thread(target=locked_var.set, args=["Bar3"]) with locked_var.lock: thread1.start() thread2.start() thread3.start() self.assertEqual("Foo", locked_var.val) thread1.join() thread2.join() thread3.join() self.assertEqual("Bar", locked_var.get()[0:3]) @unittest.skipUnless("unit" in limits, "Unit skipped") class TradeStoreTest(WebMockTestCase): def test_compute_trades(self): self.m.balances.currencies.return_value = ["XMR", "DASH", "XVG", "BTC", "ETH"] values_in_base = { "XMR": portfolio.Amount("BTC", D("0.9")), "DASH": portfolio.Amount("BTC", D("0.4")), "XVG": portfolio.Amount("BTC", D("-0.5")), "BTC": portfolio.Amount("BTC", D("0.5")), } new_repartition = { "DASH": portfolio.Amount("BTC", D("0.5")), "XVG": portfolio.Amount("BTC", D("0.1")), "BTC": portfolio.Amount("BTC", D("0.4")), "ETH": portfolio.Amount("BTC", D("0.3")), } side_effect = [ (True, 1), (False, 2), (False, 3), (True, 4), (True, 5) ] with mock.patch.object(market.TradeStore, "trade_if_matching") as trade_if_matching: trade_store = market.TradeStore(self.m) trade_if_matching.side_effect = side_effect trade_store.compute_trades(values_in_base, new_repartition, only="only") self.assertEqual(5, trade_if_matching.call_count) self.assertEqual(3, len(trade_store.all)) self.assertEqual([1, 4, 5], trade_store.all) self.m.report.log_trades.assert_called_with(side_effect, "only") def test_trade_if_matching(self): with self.subTest(only="nope"): trade_store = market.TradeStore(self.m) result = trade_store.trade_if_matching( portfolio.Amount("BTC", D("0")), portfolio.Amount("BTC", D("0.3")), "ETH", only="nope") self.assertEqual(False, result[0]) self.assertIsInstance(result[1], portfolio.Trade) with self.subTest(only=None): trade_store = market.TradeStore(self.m) result = trade_store.trade_if_matching( portfolio.Amount("BTC", D("0")), portfolio.Amount("BTC", D("0.3")), "ETH", only=None) self.assertEqual(True, result[0]) with self.subTest(only="acquire"): trade_store = market.TradeStore(self.m) result = trade_store.trade_if_matching( portfolio.Amount("BTC", D("0")), portfolio.Amount("BTC", D("0.3")), "ETH", only="acquire") self.assertEqual(True, result[0]) with self.subTest(only="dispose"): trade_store = market.TradeStore(self.m) result = trade_store.trade_if_matching( portfolio.Amount("BTC", D("0")), portfolio.Amount("BTC", D("0.3")), "ETH", only="dispose") self.assertEqual(False, result[0]) def test_prepare_orders(self): trade_store = market.TradeStore(self.m) trade_mock1 = mock.Mock() trade_mock2 = mock.Mock() trade_mock3 = mock.Mock() trade_mock1.prepare_order.return_value = 1 trade_mock2.prepare_order.return_value = 2 trade_mock3.prepare_order.return_value = 3 trade_mock1.pending = True trade_mock2.pending = True trade_mock3.pending = False trade_store.all.append(trade_mock1) trade_store.all.append(trade_mock2) trade_store.all.append(trade_mock3) trade_store.prepare_orders() trade_mock1.prepare_order.assert_called_with(compute_value="default") trade_mock2.prepare_order.assert_called_with(compute_value="default") trade_mock3.prepare_order.assert_not_called() self.m.report.log_orders.assert_called_once_with([1, 2], None, "default") self.m.report.log_orders.reset_mock() trade_store.prepare_orders(compute_value="bla") trade_mock1.prepare_order.assert_called_with(compute_value="bla") trade_mock2.prepare_order.assert_called_with(compute_value="bla") self.m.report.log_orders.assert_called_once_with([1, 2], None, "bla") trade_mock1.prepare_order.reset_mock() trade_mock2.prepare_order.reset_mock() self.m.report.log_orders.reset_mock() trade_mock1.action = "foo" trade_mock2.action = "bar" trade_store.prepare_orders(only="bar") trade_mock1.prepare_order.assert_not_called() trade_mock2.prepare_order.assert_called_with(compute_value="default") self.m.report.log_orders.assert_called_once_with([2], "bar", "default") def test_print_all_with_order(self): trade_mock1 = mock.Mock() trade_mock2 = mock.Mock() trade_mock3 = mock.Mock() trade_store = market.TradeStore(self.m) trade_store.all = [trade_mock1, trade_mock2, trade_mock3] trade_store.print_all_with_order() trade_mock1.print_with_order.assert_called() trade_mock2.print_with_order.assert_called() trade_mock3.print_with_order.assert_called() def test_run_orders(self): with mock.patch.object(market.TradeStore, "all_orders") as all_orders: order_mock1 = mock.Mock() order_mock2 = mock.Mock() order_mock3 = mock.Mock() trade_store = market.TradeStore(self.m) all_orders.return_value = [order_mock1, order_mock2, order_mock3] trade_store.run_orders() all_orders.assert_called_with(state="pending") order_mock1.run.assert_called() order_mock2.run.assert_called() order_mock3.run.assert_called() self.m.report.log_stage.assert_called_with("run_orders") self.m.report.log_orders.assert_called_with([order_mock1, order_mock2, order_mock3]) def test_all_orders(self): trade_mock1 = mock.Mock() trade_mock2 = mock.Mock() order_mock1 = mock.Mock() order_mock2 = mock.Mock() order_mock3 = mock.Mock() trade_mock1.orders = [order_mock1, order_mock2] trade_mock2.orders = [order_mock3] order_mock1.status = "pending" order_mock2.status = "open" order_mock3.status = "open" trade_store = market.TradeStore(self.m) trade_store.all.append(trade_mock1) trade_store.all.append(trade_mock2) orders = trade_store.all_orders() self.assertEqual(3, len(orders)) open_orders = trade_store.all_orders(state="open") self.assertEqual(2, len(open_orders)) self.assertEqual([order_mock2, order_mock3], open_orders) def test_update_all_orders_status(self): with mock.patch.object(market.TradeStore, "all_orders") as all_orders: order_mock1 = mock.Mock() order_mock2 = mock.Mock() order_mock3 = mock.Mock() all_orders.return_value = [order_mock1, order_mock2, order_mock3] trade_store = market.TradeStore(self.m) trade_store.update_all_orders_status() all_orders.assert_called_with(state="open") order_mock1.get_status.assert_called() order_mock2.get_status.assert_called() order_mock3.get_status.assert_called() def test_close_trades(self): trade_mock1 = mock.Mock() trade_mock2 = mock.Mock() trade_mock3 = mock.Mock() trade_store = market.TradeStore(self.m) trade_store.all.append(trade_mock1) trade_store.all.append(trade_mock2) trade_store.all.append(trade_mock3) trade_store.close_trades() trade_mock1.close.assert_called_once_with() trade_mock2.close.assert_called_once_with() trade_mock3.close.assert_called_once_with() def test_pending(self): trade_mock1 = mock.Mock() trade_mock1.pending = True trade_mock2 = mock.Mock() trade_mock2.pending = True trade_mock3 = mock.Mock() trade_mock3.pending = False trade_store = market.TradeStore(self.m) trade_store.all.append(trade_mock1) trade_store.all.append(trade_mock2) trade_store.all.append(trade_mock3) self.assertEqual([trade_mock1, trade_mock2], trade_store.pending) @unittest.skipUnless("unit" in limits, "Unit skipped") class BalanceStoreTest(WebMockTestCase): def setUp(self): super().setUp() self.fetch_balance = { "ETC": { "exchange_free": 0, "exchange_used": 0, "exchange_total": 0, "margin_total": 0, }, "USDT": { "exchange_free": D("6.0"), "exchange_used": D("1.2"), "exchange_total": D("7.2"), "margin_total": 0, }, "XVG": { "exchange_free": 16, "exchange_used": 0, "exchange_total": 16, "margin_total": 0, }, "XMR": { "exchange_free": 0, "exchange_used": 0, "exchange_total": 0, "margin_total": D("-1.0"), "margin_free": 0, }, } def test_in_currency(self): self.m.get_ticker.return_value = { "bid": D("0.09"), "ask": D("0.11"), "average": D("0.1"), } balance_store = market.BalanceStore(self.m) balance_store.all = { "BTC": portfolio.Balance("BTC", { "total": "0.65", "exchange_total":"0.65", "exchange_free": "0.35", "exchange_used": "0.30"}), "ETH": portfolio.Balance("ETH", { "total": 3, "exchange_total": 3, "exchange_free": 3, "exchange_used": 0}), } amounts = balance_store.in_currency("BTC") self.assertEqual("BTC", amounts["ETH"].currency) self.assertEqual(D("0.65"), amounts["BTC"].value) self.assertEqual(D("0.30"), amounts["ETH"].value) self.m.report.log_tickers.assert_called_once_with(amounts, "BTC", "average", "total") self.m.report.log_tickers.reset_mock() amounts = balance_store.in_currency("BTC", compute_value="bid") self.assertEqual(D("0.65"), amounts["BTC"].value) self.assertEqual(D("0.27"), amounts["ETH"].value) self.m.report.log_tickers.assert_called_once_with(amounts, "BTC", "bid", "total") self.m.report.log_tickers.reset_mock() amounts = balance_store.in_currency("BTC", compute_value="bid", type="exchange_used") self.assertEqual(D("0.30"), amounts["BTC"].value) self.assertEqual(0, amounts["ETH"].value) self.m.report.log_tickers.assert_called_once_with(amounts, "BTC", "bid", "exchange_used") self.m.report.log_tickers.reset_mock() def test_fetch_balances(self): self.m.ccxt.fetch_all_balances.return_value = self.fetch_balance balance_store = market.BalanceStore(self.m) with self.subTest(log_tickers=False): balance_store.fetch_balances() self.assertNotIn("ETC", balance_store.currencies()) self.assertListEqual(["USDT", "XVG", "XMR"], list(balance_store.currencies())) balance_store.all["ETC"] = portfolio.Balance("ETC", { "exchange_total": "1", "exchange_free": "0", "exchange_used": "1" }) balance_store.fetch_balances(tag="foo") self.assertEqual(0, balance_store.all["ETC"].total) self.assertListEqual(["USDT", "XVG", "XMR", "ETC"], list(balance_store.currencies())) self.m.report.log_balances.assert_called_with(tag="foo", checkpoint=None) with self.subTest(log_tickers=True),\ mock.patch.object(balance_store, "in_currency") as in_currency: in_currency.return_value = "tickers" balance_store.fetch_balances(log_tickers=True, ticker_currency="FOO", ticker_compute_value="compute", ticker_type="type") self.m.report.log_balances.assert_called_with(compute_value='compute', tag=None, checkpoint=None, ticker_currency='FOO', tickers='tickers', type='type') balance_store = market.BalanceStore(self.m) with self.subTest(add_portfolio=True),\ mock.patch.object(market.Portfolio, "repartition") as repartition: repartition.return_value = { "DOGE": D("0.5"), "USDT": D("0.5"), } balance_store.fetch_balances(add_portfolio=True) self.assertListEqual(["USDT", "XVG", "XMR", "DOGE"], list(balance_store.currencies())) self.m.ccxt.fetch_all_balances.return_value = { "ETC": { "exchange_free": 0, "exchange_used": 0, "exchange_total": 0, "margin_total": 0, }, "XVG": { "exchange_free": 16, "exchange_used": 0, "exchange_total": 16, "margin_total": 0, }, "XMR": { "exchange_free": 0, "exchange_used": 0, "exchange_total": 0, "margin_total": D("-1.0"), "margin_free": 0, }, } balance_store = market.BalanceStore(self.m) with self.subTest(add_usdt=True),\ mock.patch.object(market.Portfolio, "repartition") as repartition: repartition.return_value = { "DOGE": D("0.5"), "ETH": D("0.5"), } balance_store.fetch_balances(add_usdt=True) self.assertListEqual(["XVG", "XMR", "USDT"], list(balance_store.currencies())) @mock.patch.object(market.Portfolio, "repartition") def test_available_balances_for_repartition(self, repartition): with self.subTest(available_balance_only=True): def _get_ticker(c1, c2): if c1 == "ZRC" and c2 == "BTC": return { "average": D("0.0001") } if c1 == "DOGE" and c2 == "BTC": return { "average": D("0.000001") } if c1 == "ETH" and c2 == "BTC": return { "average": D("0.1") } if c1 == "FOO" and c2 == "BTC": return { "average": D("0.1") } self.fail("Should not be called with {}, {}".format(c1, c2)) self.m.get_ticker.side_effect = _get_ticker repartition.return_value = { "DOGE": (D("0.20"), "short"), "BTC": (D("0.20"), "long"), "ETH": (D("0.20"), "long"), "XMR": (D("0.20"), "long"), "FOO": (D("0.20"), "long"), } self.m.ccxt.fetch_all_balances.return_value = { "ZRC": { "exchange_free": D("2.0"), "exchange_used": D("0.0"), "exchange_total": D("2.0"), "total": D("2.0") }, "DOGE": { "exchange_free": D("5.0"), "exchange_used": D("0.0"), "exchange_total": D("5.0"), "total": D("5.0") }, "BTC": { "exchange_free": D("0.065"), "exchange_used": D("0.02"), "exchange_total": D("0.085"), "margin_available": D("0.035"), "margin_in_position": D("0.01"), "margin_total": D("0.045"), "total": D("0.13") }, "ETH": { "exchange_free": D("1.0"), "exchange_used": D("0.0"), "exchange_total": D("1.0"), "total": D("1.0") }, "FOO": { "exchange_free": D("0.1"), "exchange_used": D("0.0"), "exchange_total": D("0.1"), "total": D("0.1"), }, } balance_store = market.BalanceStore(self.m) balance_store.fetch_balances() _repartition, total_base_value, amount_in_position = balance_store.available_balances_for_repartition() repartition.assert_called_with(liquidity="medium") self.assertEqual((D("0.20"), "short"), _repartition["DOGE"]) self.assertEqual((D("0.20"), "long"), _repartition["BTC"]) self.assertEqual((D("0.20"), "long"), _repartition["XMR"]) self.assertEqual((D("0.20"), "long"), _repartition["FOO"]) self.assertIsNone(_repartition.get("ETH")) self.assertEqual(portfolio.Amount("BTC", "0.1"), total_base_value) self.assertEqual(0, amount_in_position["DOGE"]) self.assertEqual(0, amount_in_position["BTC"]) self.assertEqual(0, amount_in_position["XMR"]) self.assertEqual(portfolio.Amount("BTC", "0.1"), amount_in_position["ETH"]) self.assertEqual(portfolio.Amount("BTC", "0.01"), amount_in_position["FOO"]) with self.subTest(available_balance_only=True, balance=0): def _get_ticker(c1, c2): if c1 == "ETH" and c2 == "BTC": return { "average": D("0.1") } self.fail("Should not be called with {}, {}".format(c1, c2)) self.m.get_ticker.side_effect = _get_ticker repartition.return_value = { "BTC": (D("0.5"), "long"), "ETH": (D("0.5"), "long"), } self.m.ccxt.fetch_all_balances.return_value = { "ETH": { "exchange_free": D("1.0"), "exchange_used": D("0.0"), "exchange_total": D("1.0"), "total": D("1.0") }, } balance_store = market.BalanceStore(self.m) balance_store.fetch_balances() _repartition, total_base_value, amount_in_position = balance_store.available_balances_for_repartition(liquidity="high") repartition.assert_called_with(liquidity="high") self.assertEqual((D("0.5"), "long"), _repartition["BTC"]) self.assertIsNone(_repartition.get("ETH")) self.assertEqual(0, total_base_value) self.assertEqual(0, amount_in_position["BTC"]) self.assertEqual(0, amount_in_position["BTC"]) repartition.reset_mock() with self.subTest(available_balance_only=True, balance=0, repartition="present"): def _get_ticker(c1, c2): if c1 == "ETH" and c2 == "BTC": return { "average": D("0.1") } self.fail("Should not be called with {}, {}".format(c1, c2)) self.m.get_ticker.side_effect = _get_ticker _repartition = { "BTC": (D("0.5"), "long"), "ETH": (D("0.5"), "long"), } self.m.ccxt.fetch_all_balances.return_value = { "ETH": { "exchange_free": D("1.0"), "exchange_used": D("0.0"), "exchange_total": D("1.0"), "total": D("1.0") }, } balance_store = market.BalanceStore(self.m) balance_store.fetch_balances() _repartition, total_base_value, amount_in_position = balance_store.available_balances_for_repartition(repartition=_repartition) repartition.assert_not_called() self.assertEqual((D("0.5"), "long"), _repartition["BTC"]) self.assertIsNone(_repartition.get("ETH")) self.assertEqual(0, total_base_value) self.assertEqual(0, amount_in_position["BTC"]) self.assertEqual(portfolio.Amount("BTC", "0.1"), amount_in_position["ETH"]) repartition.reset_mock() with self.subTest(available_balance_only=True, balance=0, repartition="present", base_currency="ETH"): def _get_ticker(c1, c2): if c1 == "ETH" and c2 == "BTC": return { "average": D("0.1") } self.fail("Should not be called with {}, {}".format(c1, c2)) self.m.get_ticker.side_effect = _get_ticker _repartition = { "BTC": (D("0.5"), "long"), "ETH": (D("0.5"), "long"), } self.m.ccxt.fetch_all_balances.return_value = { "ETH": { "exchange_free": D("1.0"), "exchange_used": D("0.0"), "exchange_total": D("1.0"), "total": D("1.0") }, } balance_store = market.BalanceStore(self.m) balance_store.fetch_balances() _repartition, total_base_value, amount_in_position = balance_store.available_balances_for_repartition(repartition=_repartition, base_currency="ETH") self.assertEqual((D("0.5"), "long"), _repartition["BTC"]) self.assertEqual((D("0.5"), "long"), _repartition["ETH"]) self.assertEqual(portfolio.Amount("ETH", 1), total_base_value) self.assertEqual(0, amount_in_position["BTC"]) self.assertEqual(0, amount_in_position["ETH"]) @mock.patch.object(market.Portfolio, "repartition") def test_dispatch_assets(self, repartition): self.m.ccxt.fetch_all_balances.return_value = self.fetch_balance balance_store = market.BalanceStore(self.m) balance_store.fetch_balances() self.assertNotIn("XEM", balance_store.currencies()) repartition_hash = { "XEM": (D("0.75"), "long"), "BTC": (D("0.26"), "long"), "DASH": (D("0.10"), "short"), } repartition.return_value = repartition_hash amounts = balance_store.dispatch_assets(portfolio.Amount("BTC", "11.1")) repartition.assert_called_with(liquidity="medium") self.assertIn("XEM", balance_store.currencies()) self.assertEqual(D("2.6"), amounts["BTC"].value) self.assertEqual(D("7.5"), amounts["XEM"].value) self.assertEqual(D("-1.0"), amounts["DASH"].value) self.m.report.log_balances.assert_called_with(tag=None, checkpoint=None) self.m.report.log_dispatch.assert_called_once_with(portfolio.Amount("BTC", "11.1"), amounts, "medium", repartition_hash) def test_currencies(self): balance_store = market.BalanceStore(self.m) balance_store.all = { "BTC": portfolio.Balance("BTC", { "total": "0.65", "exchange_total":"0.65", "exchange_free": "0.35", "exchange_used": "0.30"}), "ETH": portfolio.Balance("ETH", { "total": 3, "exchange_total": 3, "exchange_free": 3, "exchange_used": 0}), } self.assertListEqual(["BTC", "ETH"], list(balance_store.currencies())) def test_as_json(self): balance_mock1 = mock.Mock() balance_mock1.as_json.return_value = 1 balance_mock2 = mock.Mock() balance_mock2.as_json.return_value = 2 balance_store = market.BalanceStore(self.m) balance_store.all = { "BTC": balance_mock1, "ETH": balance_mock2, } as_json = balance_store.as_json() self.assertEqual(1, as_json["BTC"]) self.assertEqual(2, as_json["ETH"]) @unittest.skipUnless("unit" in limits, "Unit skipped") class ReportStoreTest(WebMockTestCase): def test_add_log(self): with self.subTest(market=self.m): self.m.user_id = 1 self.m.market_id = 3 report_store = market.ReportStore(self.m) result = report_store.add_log({"foo": "bar"}) self.assertEqual({"foo": "bar", "date": mock.ANY, "user_id": 1, "market_id": 3}, result) self.assertEqual(result, report_store.logs[0]) with self.subTest(market=None): report_store = market.ReportStore(None) result = report_store.add_log({"foo": "bar"}) self.assertEqual({"foo": "bar", "date": mock.ANY, "user_id": None, "market_id": None}, result) def test_add_redis_status(self): report_store = market.ReportStore(self.m) result = report_store.add_redis_status({"foo": "bar"}) self.assertEqual({"foo": "bar"}, result) self.assertEqual(result, report_store.redis_status[0]) def test_set_verbose(self): report_store = market.ReportStore(self.m) with self.subTest(verbose=True): report_store.set_verbose(True) self.assertTrue(report_store.verbose_print) with self.subTest(verbose=False): report_store.set_verbose(False) self.assertFalse(report_store.verbose_print) def test_merge(self): self.m.user_id = 1 self.m.market_id = 3 report_store1 = market.ReportStore(self.m, verbose_print=False) report_store2 = market.ReportStore(None, verbose_print=False) report_store2.log_stage("1") report_store1.log_stage("2") report_store2.log_stage("3") report_store1.merge(report_store2) self.assertEqual(3, len(report_store1.logs)) self.assertEqual(["1", "2", "3"], list(map(lambda x: x["stage"], report_store1.logs))) self.assertEqual(6, len(report_store1.print_logs)) def test_print_log(self): report_store = market.ReportStore(self.m) with self.subTest(verbose=True),\ mock.patch.object(store, "datetime") as time_mock,\ mock.patch('sys.stdout', new_callable=StringIO) as stdout_mock: time_mock.datetime.now.return_value = datetime.datetime(2018, 2, 25, 2, 20, 10) report_store.set_verbose(True) report_store.print_log("Coucou") report_store.print_log(portfolio.Amount("BTC", 1)) self.assertEqual(stdout_mock.getvalue(), "2018-02-25 02:20:10: Coucou\n2018-02-25 02:20:10: 1.00000000 BTC\n") with self.subTest(verbose=False),\ mock.patch('sys.stdout', new_callable=StringIO) as stdout_mock: report_store.set_verbose(False) report_store.print_log("Coucou") report_store.print_log(portfolio.Amount("BTC", 1)) self.assertEqual(stdout_mock.getvalue(), "") def test_default_json_serial(self): report_store = market.ReportStore(self.m) self.assertEqual("2018-02-24T00:00:00", report_store.default_json_serial(portfolio.datetime.datetime(2018, 2, 24))) self.assertEqual("1.00000000 BTC", report_store.default_json_serial(portfolio.Amount("BTC", 1))) def test_to_json(self): report_store = market.ReportStore(self.m) report_store.logs.append({"foo": "bar"}) self.assertEqual('[\n {\n "foo": "bar"\n }\n]', report_store.to_json()) report_store.logs.append({"date": portfolio.datetime.datetime(2018, 2, 24)}) self.assertEqual('[\n {\n "foo": "bar"\n },\n {\n "date": "2018-02-24T00:00:00"\n }\n]', report_store.to_json()) report_store.logs.append({"amount": portfolio.Amount("BTC", 1)}) self.assertEqual('[\n {\n "foo": "bar"\n },\n {\n "date": "2018-02-24T00:00:00"\n },\n {\n "amount": "1.00000000 BTC"\n }\n]', report_store.to_json()) def test_to_json_array(self): report_store = market.ReportStore(self.m) report_store.logs.append({ "date": "date1", "type": "type1", "foo": "bar", "bla": "bla" }) report_store.logs.append({ "date": "date2", "type": "type2", "foo": "bar", "bla": "bla" }) logs = list(report_store.to_json_array()) self.assertEqual(2, len(logs)) self.assertEqual(("date1", "type1", '{\n "foo": "bar",\n "bla": "bla"\n}'), logs[0]) self.assertEqual(("date2", "type2", '{\n "foo": "bar",\n "bla": "bla"\n}'), logs[1]) def test_to_json_redis(self): report_store = market.ReportStore(self.m) report_store.redis_status.append({ "type": "type1", "foo": "bar", "bla": "bla" }) report_store.redis_status.append({ "type": "type2", "foo": "bar", "bla": "bla" }) logs = list(report_store.to_json_redis()) self.assertEqual(2, len(logs)) self.assertEqual(("type1", '{"foo": "bar", "bla": "bla"}'), logs[0]) self.assertEqual(("type2", '{"foo": "bar", "bla": "bla"}'), logs[1]) @mock.patch.object(market.ReportStore, "print_log") @mock.patch.object(market.ReportStore, "add_log") def test_log_stage(self, add_log, print_log): report_store = market.ReportStore(self.m) c = lambda x: x report_store.log_stage("foo", bar="baz", c=c, d=portfolio.Amount("BTC", 1)) print_log.assert_has_calls([ mock.call("-----------"), mock.call("[Stage] foo bar=baz, c=c = lambda x: x, d={'currency': 'BTC', 'value': Decimal('1')}"), ]) add_log.assert_called_once_with({ 'type': 'stage', 'stage': 'foo', 'args': { 'bar': 'baz', 'c': 'c = lambda x: x', 'd': { 'currency': 'BTC', 'value': D('1') } } }) @mock.patch.object(market.ReportStore, "print_log") @mock.patch.object(market.ReportStore, "add_log") @mock.patch.object(market.ReportStore, "add_redis_status") def test_log_balances(self, add_redis_status, add_log, print_log): report_store = market.ReportStore(self.m) self.m.balances.as_json.return_value = "json" self.m.balances.all = { "FOO": "bar", "BAR": "baz" } with self.subTest(tickers=None): report_store.log_balances(tag="tag") print_log.assert_has_calls([ mock.call("[Balance]"), mock.call("\tbar"), mock.call("\tbaz"), ]) add_log.assert_called_once_with({ 'type': 'balance', 'checkpoint': None, 'balances': 'json', 'tag': 'tag' }) add_redis_status.assert_called_once_with({ 'type': 'balance', 'balances': 'json', 'checkpoint': None, 'tag': 'tag' }) add_log.reset_mock() add_redis_status.reset_mock() with self.subTest(tickers="present"): amounts = { "BTC": portfolio.Amount("BTC", 10), "ETH": portfolio.Amount("BTC", D("0.3")) } amounts["ETH"].rate = D("0.1") report_store.log_balances(tag="tag", tickers=amounts, ticker_currency="BTC", compute_value="default", type="total") add_log.assert_called_once_with({ 'type': 'balance', 'checkpoint': None, 'balances': 'json', 'tag': 'tag', 'tickers': { 'compute_value': 'default', 'balance_type': 'total', 'currency': 'BTC', 'balances': { 'BTC': D('10'), 'ETH': D('0.3') }, 'rates': { 'BTC': None, 'ETH': D('0.1') }, 'total': D('10.3') }, }) add_redis_status.assert_called_once_with({ 'type': 'balance', 'checkpoint': None, 'balances': 'json', 'tag': 'tag', 'tickers': { 'compute_value': 'default', 'balance_type': 'total', 'currency': 'BTC', 'balances': { 'BTC': D('10'), 'ETH': D('0.3') }, 'rates': { 'BTC': None, 'ETH': D('0.1') }, 'total': D('10.3') }, }) @mock.patch.object(market.ReportStore, "print_log") @mock.patch.object(market.ReportStore, "add_log") def test_log_tickers(self, add_log, print_log): report_store = market.ReportStore(self.m) amounts = { "BTC": portfolio.Amount("BTC", 10), "ETH": portfolio.Amount("BTC", D("0.3")) } amounts["ETH"].rate = D("0.1") report_store.log_tickers(amounts, "BTC", "default", "total") print_log.assert_not_called() add_log.assert_called_once_with({ 'type': 'tickers', 'compute_value': 'default', 'balance_type': 'total', 'currency': 'BTC', 'balances': { 'BTC': D('10'), 'ETH': D('0.3') }, 'rates': { 'BTC': None, 'ETH': D('0.1') }, 'total': D('10.3') }) add_log.reset_mock() compute_value = lambda x: x["bid"] report_store.log_tickers(amounts, "BTC", compute_value, "total") add_log.assert_called_once_with({ 'type': 'tickers', 'compute_value': 'compute_value = lambda x: x["bid"]', 'balance_type': 'total', 'currency': 'BTC', 'balances': { 'BTC': D('10'), 'ETH': D('0.3') }, 'rates': { 'BTC': None, 'ETH': D('0.1') }, 'total': D('10.3') }) @mock.patch.object(market.ReportStore, "print_log") @mock.patch.object(market.ReportStore, "add_log") def test_log_dispatch(self, add_log, print_log): report_store = market.ReportStore(self.m) amount = portfolio.Amount("BTC", "10.3") amounts = { "BTC": portfolio.Amount("BTC", 10), "ETH": portfolio.Amount("BTC", D("0.3")) } report_store.log_dispatch(amount, amounts, "medium", "repartition") print_log.assert_not_called() add_log.assert_called_once_with({ 'type': 'dispatch', 'liquidity': 'medium', 'repartition_ratio': 'repartition', 'total_amount': { 'currency': 'BTC', 'value': D('10.3') }, 'repartition': { 'BTC': D('10'), 'ETH': D('0.3') } }) @mock.patch.object(market.ReportStore, "print_log") @mock.patch.object(market.ReportStore, "add_log") def test_log_trades(self, add_log, print_log): report_store = market.ReportStore(self.m) trade_mock1 = mock.Mock() trade_mock2 = mock.Mock() trade_mock1.as_json.return_value = { "trade": "1" } trade_mock2.as_json.return_value = { "trade": "2" } matching_and_trades = [ (True, trade_mock1), (False, trade_mock2), ] report_store.log_trades(matching_and_trades, "only") print_log.assert_not_called() add_log.assert_called_with({ 'type': 'trades', 'only': 'only', 'debug': False, 'trades': [ {'trade': '1', 'skipped': False}, {'trade': '2', 'skipped': True} ] }) @mock.patch.object(market.ReportStore, "print_log") @mock.patch.object(market.ReportStore, "add_log") def test_log_orders(self, add_log, print_log): report_store = market.ReportStore(self.m) order_mock1 = mock.Mock() order_mock2 = mock.Mock() order_mock1.as_json.return_value = "order1" order_mock2.as_json.return_value = "order2" orders = [order_mock1, order_mock2] report_store.log_orders(orders, tick="tick", only="only", compute_value="compute_value") print_log.assert_called_once_with("[Orders]") self.m.trades.print_all_with_order.assert_called_once_with(ind="\t") add_log.assert_called_with({ 'type': 'orders', 'only': 'only', 'compute_value': 'compute_value', 'tick': 'tick', 'orders': ['order1', 'order2'] }) add_log.reset_mock() def compute_value(x, y): return x[y] report_store.log_orders(orders, tick="tick", only="only", compute_value=compute_value) add_log.assert_called_with({ 'type': 'orders', 'only': 'only', 'compute_value': 'def compute_value(x, y):\n return x[y]', 'tick': 'tick', 'orders': ['order1', 'order2'] }) @mock.patch.object(market.ReportStore, "print_log") @mock.patch.object(market.ReportStore, "add_log") def test_log_order(self, add_log, print_log): report_store = market.ReportStore(self.m) order_mock = mock.Mock() order_mock.as_json.return_value = "order" new_order_mock = mock.Mock() new_order_mock.as_json.return_value = "new_order" order_mock.__repr__ = mock.Mock() order_mock.__repr__.return_value = "Order Mock" new_order_mock.__repr__ = mock.Mock() new_order_mock.__repr__.return_value = "New order Mock" with self.subTest(finished=True): report_store.log_order(order_mock, 1, finished=True) print_log.assert_called_once_with("[Order] Finished Order Mock") add_log.assert_called_once_with({ 'type': 'order', 'tick': 1, 'update': None, 'order': 'order', 'compute_value': None, 'new_order': None }) add_log.reset_mock() print_log.reset_mock() with self.subTest(update="waiting"): report_store.log_order(order_mock, 1, update="waiting") print_log.assert_called_once_with("[Order] Order Mock, tick 1, waiting") add_log.assert_called_once_with({ 'type': 'order', 'tick': 1, 'update': 'waiting', 'order': 'order', 'compute_value': None, 'new_order': None }) add_log.reset_mock() print_log.reset_mock() with self.subTest(update="adjusting"): compute_value = lambda x: (x["bid"] + x["ask"]*2)/3 report_store.log_order(order_mock, 3, update="adjusting", new_order=new_order_mock, compute_value=compute_value) print_log.assert_called_once_with("[Order] Order Mock, tick 3, cancelling and adjusting to New order Mock") add_log.assert_called_once_with({ 'type': 'order', 'tick': 3, 'update': 'adjusting', 'order': 'order', 'compute_value': 'compute_value = lambda x: (x["bid"] + x["ask"]*2)/3', 'new_order': 'new_order' }) add_log.reset_mock() print_log.reset_mock() with self.subTest(update="market_fallback"): report_store.log_order(order_mock, 7, update="market_fallback", new_order=new_order_mock) print_log.assert_called_once_with("[Order] Order Mock, tick 7, fallbacking to market value") add_log.assert_called_once_with({ 'type': 'order', 'tick': 7, 'update': 'market_fallback', 'order': 'order', 'compute_value': None, 'new_order': 'new_order' }) add_log.reset_mock() print_log.reset_mock() with self.subTest(update="market_adjusting"): report_store.log_order(order_mock, 17, update="market_adjust", new_order=new_order_mock) print_log.assert_called_once_with("[Order] Order Mock, tick 17, market value, cancelling and adjusting to New order Mock") add_log.assert_called_once_with({ 'type': 'order', 'tick': 17, 'update': 'market_adjust', 'order': 'order', 'compute_value': None, 'new_order': 'new_order' }) @mock.patch.object(market.ReportStore, "print_log") @mock.patch.object(market.ReportStore, "add_log") def test_log_move_balances(self, add_log, print_log): report_store = market.ReportStore(self.m) needed = { "BTC": portfolio.Amount("BTC", 10), "USDT": 1 } moving = { "BTC": portfolio.Amount("BTC", 3), "USDT": -2 } report_store.log_move_balances(needed, moving) print_log.assert_not_called() add_log.assert_called_once_with({ 'type': 'move_balances', 'debug': False, 'needed': { 'BTC': D('10'), 'USDT': 1 }, 'moving': { 'BTC': D('3'), 'USDT': -2 } }) def test_log_http_request(self): with mock.patch.object(market.ReportStore, "add_log") as add_log: report_store = market.ReportStore(self.m) response = mock.Mock() response.status_code = 200 response.text = "Hey" response.elapsed.total_seconds.return_value = 120 report_store.log_http_request("method", "url", "body", "headers", response) add_log.assert_called_once_with({ 'type': 'http_request', 'method': 'method', 'url': 'url', 'body': 'body', 'headers': 'headers', 'status': 200, 'duration': 120, 'response': 'Hey', 'response_same_as': None, }) 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', }) with self.subTest(no_http_dup=True, duplicate=True): self.m.user_id = 1 self.m.market_id = 3 report_store = market.ReportStore(self.m, no_http_dup=True) original_add_log = report_store.add_log with mock.patch.object(report_store, "add_log", side_effect=original_add_log) as add_log: report_store.log_http_request("method", "url", "body", "headers", response) report_store.log_http_request("method", "url", "body", "headers", response) self.assertEqual(2, add_log.call_count) self.assertIsNone(add_log.mock_calls[0][1][0]["response_same_as"]) self.assertIsNone(add_log.mock_calls[1][1][0]["response"]) self.assertEqual(add_log.mock_calls[0][1][0]["date"], add_log.mock_calls[1][1][0]["response_same_as"]) with self.subTest(no_http_dup=True, duplicate=False, case="Different call"): self.m.user_id = 1 self.m.market_id = 3 report_store = market.ReportStore(self.m, no_http_dup=True) original_add_log = report_store.add_log with mock.patch.object(report_store, "add_log", side_effect=original_add_log) as add_log: report_store.log_http_request("method", "url", "body", "headers", response) report_store.log_http_request("method2", "url", "body", "headers", response) self.assertEqual(2, add_log.call_count) self.assertIsNone(add_log.mock_calls[0][1][0]["response_same_as"]) self.assertIsNone(add_log.mock_calls[1][1][0]["response_same_as"]) with self.subTest(no_http_dup=True, duplicate=False, case="Call inbetween"): self.m.user_id = 1 self.m.market_id = 3 report_store = market.ReportStore(self.m, no_http_dup=True) original_add_log = report_store.add_log response2 = mock.Mock() response2.status_code = 200 response2.text = "Hey there!" with mock.patch.object(report_store, "add_log", side_effect=original_add_log) as add_log: report_store.log_http_request("method", "url", "body", "headers", response) report_store.log_http_request("method", "url", "body", "headers", response2) report_store.log_http_request("method", "url", "body", "headers", response) self.assertEqual(3, add_log.call_count) self.assertIsNone(add_log.mock_calls[0][1][0]["response_same_as"]) self.assertIsNone(add_log.mock_calls[1][1][0]["response_same_as"]) self.assertIsNone(add_log.mock_calls[2][1][0]["response_same_as"]) @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)) add_log.assert_called_once_with({ "type": "market", "commit": "$Format:%H$", "args": { "report_path": None, "debug": True, "quiet": False }, }) @mock.patch.object(market.ReportStore, "print_log") @mock.patch.object(market.ReportStore, "add_log") def test_log_error(self, add_log, print_log): report_store = market.ReportStore(self.m) with self.subTest(message=None, exception=None): report_store.log_error("action") print_log.assert_called_once_with("[Error] action") add_log.assert_called_once_with({ 'type': 'error', 'action': 'action', 'exception_class': None, 'exception_message': None, 'message': None }) print_log.reset_mock() add_log.reset_mock() with self.subTest(message="Hey", exception=None): report_store.log_error("action", message="Hey") print_log.assert_has_calls([ mock.call("[Error] action"), mock.call("\tHey") ]) add_log.assert_called_once_with({ 'type': 'error', 'action': 'action', 'exception_class': None, 'exception_message': None, 'message': "Hey" }) print_log.reset_mock() add_log.reset_mock() with self.subTest(message=None, exception=Exception("bouh")): report_store.log_error("action", exception=Exception("bouh")) print_log.assert_has_calls([ mock.call("[Error] action"), mock.call("\tException: bouh") ]) add_log.assert_called_once_with({ 'type': 'error', 'action': 'action', 'exception_class': "Exception", 'exception_message': "bouh", 'message': None }) print_log.reset_mock() add_log.reset_mock() with self.subTest(message="Hey", exception=Exception("bouh")): report_store.log_error("action", message="Hey", exception=Exception("bouh")) print_log.assert_has_calls([ mock.call("[Error] action"), mock.call("\tException: bouh"), mock.call("\tHey") ]) add_log.assert_called_once_with({ 'type': 'error', 'action': 'action', 'exception_class': "Exception", 'exception_message': "bouh", 'message': "Hey" }) @mock.patch.object(market.ReportStore, "print_log") @mock.patch.object(market.ReportStore, "add_log") def test_log_debug_action(self, add_log, print_log): report_store = market.ReportStore(self.m) report_store.log_debug_action("Hey") print_log.assert_called_once_with("[Debug] Hey") add_log.assert_called_once_with({ 'type': 'debug_action', 'action': 'Hey' }) @unittest.skipUnless("unit" in limits, "Unit skipped") class PortfolioTest(WebMockTestCase): def setUp(self): super().setUp() with open("test_samples/test_portfolio.json") as example: self.json_response = example.read() self.wm.get(market.Portfolio.URL, text=self.json_response) @mock.patch.object(market.Portfolio, "parse_cryptoportfolio") @mock.patch.object(market.Portfolio, "store_cryptoportfolio") def test_get_cryptoportfolio(self, store_cryptoportfolio, parse_cryptoportfolio): with self.subTest(parallel=False): self.wm.get(market.Portfolio.URL, [ {"text":'{ "foo": "bar" }', "status_code": 200}, {"text": "System Error", "status_code": 500}, {"exc": requests.exceptions.ConnectTimeout}, ]) market.Portfolio.get_cryptoportfolio() self.assertIn("foo", market.Portfolio.data.get()) self.assertEqual("bar", market.Portfolio.data.get()["foo"]) self.assertTrue(self.wm.called) self.assertEqual(1, self.wm.call_count) market.Portfolio.report.log_error.assert_not_called() market.Portfolio.report.log_http_request.assert_called_once() parse_cryptoportfolio.assert_called_once_with() store_cryptoportfolio.assert_called_once_with() market.Portfolio.report.log_http_request.reset_mock() parse_cryptoportfolio.reset_mock() store_cryptoportfolio.reset_mock() market.Portfolio.data = store.LockedVar(None) market.Portfolio.get_cryptoportfolio() self.assertIsNone(market.Portfolio.data.get()) self.assertEqual(2, self.wm.call_count) parse_cryptoportfolio.assert_not_called() store_cryptoportfolio.assert_not_called() market.Portfolio.report.log_error.assert_not_called() market.Portfolio.report.log_http_request.assert_called_once() market.Portfolio.report.log_http_request.reset_mock() parse_cryptoportfolio.reset_mock() store_cryptoportfolio.reset_mock() market.Portfolio.data = store.LockedVar("Foo") market.Portfolio.get_cryptoportfolio() self.assertEqual(2, self.wm.call_count) parse_cryptoportfolio.assert_not_called() store_cryptoportfolio.assert_not_called() market.Portfolio.get_cryptoportfolio(refetch=True) self.assertEqual("Foo", market.Portfolio.data.get()) self.assertEqual(3, self.wm.call_count) market.Portfolio.report.log_error.assert_called_once_with("get_cryptoportfolio", exception=mock.ANY) market.Portfolio.report.log_http_request.assert_not_called() with self.subTest(parallel=True): with mock.patch.object(market.Portfolio, "is_worker_thread") as is_worker,\ mock.patch.object(market.Portfolio, "notify_and_wait") as notify: with self.subTest(worker=True): market.Portfolio.data = store.LockedVar(None) market.Portfolio.worker = mock.Mock() is_worker.return_value = True self.wm.get(market.Portfolio.URL, [ {"text":'{ "foo": "bar" }', "status_code": 200}, ]) market.Portfolio.get_cryptoportfolio() self.assertIn("foo", market.Portfolio.data.get()) parse_cryptoportfolio.reset_mock() store_cryptoportfolio.reset_mock() with self.subTest(worker=False): market.Portfolio.data = store.LockedVar(None) market.Portfolio.worker = mock.Mock() market.Portfolio.worker_started = True is_worker.return_value = False market.Portfolio.get_cryptoportfolio() notify.assert_called_once_with() parse_cryptoportfolio.assert_not_called() store_cryptoportfolio.assert_not_called() with self.subTest(worker_started=False): market.Portfolio.data = store.LockedVar(None) market.Portfolio.worker = mock.Mock() market.Portfolio.worker_started = False is_worker.return_value = False with self.assertRaises(Exception): market.Portfolio.get_cryptoportfolio() def test_parse_cryptoportfolio(self): with self.subTest(description="Normal case"): market.Portfolio.data = store.LockedVar(store.json.loads( self.json_response, parse_int=D, parse_float=D)) market.Portfolio.parse_cryptoportfolio() self.assertListEqual( ["medium", "high"], list(market.Portfolio.liquidities.get().keys())) liquidities = market.Portfolio.liquidities.get() self.assertEqual(10, len(liquidities["medium"].keys())) self.assertEqual(10, len(liquidities["high"].keys())) expected = { 'BTC': (D("0.2857"), "long"), 'DGB': (D("0.1015"), "long"), 'DOGE': (D("0.1805"), "long"), 'SC': (D("0.0623"), "long"), 'ZEC': (D("0.3701"), "long"), } date = portfolio.datetime.datetime(2018, 1, 8) self.assertDictEqual(expected, liquidities["high"][date]) expected = { 'BTC': (D("1.1102e-16"), "long"), 'ETC': (D("0.1"), "long"), 'FCT': (D("0.1"), "long"), 'GAS': (D("0.1"), "long"), 'NAV': (D("0.1"), "long"), 'OMG': (D("0.1"), "long"), 'OMNI': (D("0.1"), "long"), 'PPC': (D("0.1"), "long"), 'RIC': (D("0.1"), "long"), 'VIA': (D("0.1"), "long"), 'XCP': (D("0.1"), "long"), } self.assertDictEqual(expected, liquidities["medium"][date]) self.assertEqual(portfolio.datetime.datetime(2018, 1, 15), market.Portfolio.last_date.get()) with self.subTest(description="Missing weight"): data = store.json.loads(self.json_response, parse_int=D, parse_float=D) del(data["portfolio_2"]["weights"]) market.Portfolio.data = store.LockedVar(data) with self.assertRaises(AssertionError): market.Portfolio.parse_cryptoportfolio() @mock.patch.object(store.dbs, "redis_connected") @mock.patch.object(store.dbs, "redis") def test_store_cryptoportfolio(self, redis, redis_connected): store.Portfolio.liquidities = store.LockedVar({ "medium": { datetime.datetime(2018,3,1): "medium_2018-03-01", datetime.datetime(2018,3,8): "medium_2018-03-08", }, "high": { datetime.datetime(2018,3,1): "high_2018-03-01", datetime.datetime(2018,3,8): "high_2018-03-08", } }) store.Portfolio.last_date = store.LockedVar(datetime.datetime(2018,3,8)) with self.subTest(redis_connected=False): redis_connected.return_value = False store.Portfolio.store_cryptoportfolio() redis.set.assert_not_called() with self.subTest(redis_connected=True): redis_connected.return_value = True store.Portfolio.store_cryptoportfolio() redis.set.assert_has_calls([ mock.call("/cryptoportfolio/repartition/latest", '{"medium": "medium_2018-03-08", "high": "high_2018-03-08"}'), mock.call("/cryptoportfolio/repartition/date", "2018-03-08"), ]) @mock.patch.object(store.dbs, "redis_connected") @mock.patch.object(store.dbs, "redis") def test_retrieve_cryptoportfolio(self, redis, redis_connected): with self.subTest(redis_connected=False): redis_connected.return_value = False store.Portfolio.retrieve_cryptoportfolio() redis.get.assert_not_called() self.assertIsNone(store.Portfolio.data.get()) with self.subTest(redis_connected=True, value=None): redis_connected.return_value = True redis.get.return_value = None store.Portfolio.retrieve_cryptoportfolio() self.assertEqual(2, redis.get.call_count) redis.reset_mock() with self.subTest(redis_connected=True, value="present"): redis_connected.return_value = True redis.get.side_effect = [ b'{ "medium": "medium_repartition", "high": "high_repartition" }', b"2018-03-08" ] store.Portfolio.retrieve_cryptoportfolio() self.assertEqual(2, redis.get.call_count) self.assertEqual(datetime.datetime(2018,3,8), store.Portfolio.last_date.get()) self.assertEqual("", store.Portfolio.data.get()) expected_liquidities = { 'high': { datetime.datetime(2018, 3, 8): 'high_repartition' }, 'medium': { datetime.datetime(2018, 3, 8): 'medium_repartition' }, } self.assertEqual(expected_liquidities, store.Portfolio.liquidities.get()) @mock.patch.object(market.Portfolio, "get_cryptoportfolio") @mock.patch.object(market.Portfolio, "retrieve_cryptoportfolio") def test_repartition(self, retrieve_cryptoportfolio, get_cryptoportfolio): with self.subTest(from_cache=False): market.Portfolio.liquidities = store.LockedVar({ "medium": { "2018-03-01": ["medium_2018-03-01"], "2018-03-08": ["medium_2018-03-08"], }, "high": { "2018-03-01": ["high_2018-03-01"], "2018-03-08": ["high_2018-03-08"], } }) market.Portfolio.last_date = store.LockedVar("2018-03-08") self.assertEqual(["medium_2018-03-08"], market.Portfolio.repartition()) get_cryptoportfolio.assert_called_once_with() retrieve_cryptoportfolio.assert_not_called() self.assertEqual(["medium_2018-03-08"], market.Portfolio.repartition(liquidity="medium")) self.assertEqual(["high_2018-03-08"], market.Portfolio.repartition(liquidity="high")) retrieve_cryptoportfolio.reset_mock() get_cryptoportfolio.reset_mock() with self.subTest(from_cache=True): self.assertEqual(["medium_2018-03-08"], market.Portfolio.repartition(from_cache=True)) get_cryptoportfolio.assert_called_once_with() retrieve_cryptoportfolio.assert_called_once_with() retrieve_cryptoportfolio.reset_mock() get_cryptoportfolio.reset_mock() with self.subTest("absent liquidities"): market.Portfolio.last_date = store.LockedVar("2018-03-15") self.assertIsNone(market.Portfolio.repartition()) with self.subTest("no liquidities"): market.Portfolio.liquidities = store.LockedVar({}) market.Portfolio.last_date = store.LockedVar("2018-03-08") self.assertIsNone(market.Portfolio.repartition()) @mock.patch.object(market.time, "sleep") @mock.patch.object(market.Portfolio, "get_cryptoportfolio") @mock.patch.object(market.Portfolio, "next_wait_time") def test_wait_for_recent(self, next_wait_time, get_cryptoportfolio, sleep): self.call_count = 0 def _get(refetch=False): if self.call_count != 0: self.assertTrue(refetch) else: self.assertFalse(refetch) self.call_count += 1 market.Portfolio.last_date = store.LockedVar(store.datetime.datetime.now()\ - store.datetime.timedelta(10)\ + store.datetime.timedelta(self.call_count)) get_cryptoportfolio.side_effect = _get next_wait_time.return_value = 30 market.Portfolio.wait_for_recent() sleep.assert_called_with(30) self.assertEqual(6, sleep.call_count) self.assertEqual(7, get_cryptoportfolio.call_count) market.Portfolio.report.print_log.assert_called_with("Attempt to fetch up-to-date cryptoportfolio") sleep.reset_mock() get_cryptoportfolio.reset_mock() market.Portfolio.last_date = store.LockedVar(None) self.call_count = 0 market.Portfolio.wait_for_recent(delta=15) sleep.assert_not_called() self.assertEqual(1, get_cryptoportfolio.call_count) sleep.reset_mock() get_cryptoportfolio.reset_mock() market.Portfolio.last_date = store.LockedVar(None) self.call_count = 0 market.Portfolio.wait_for_recent(delta=1) sleep.assert_called_with(30) self.assertEqual(9, sleep.call_count) self.assertEqual(10, get_cryptoportfolio.call_count) def test_is_worker_thread(self): with self.subTest(worker=None): self.assertFalse(store.Portfolio.is_worker_thread()) with self.subTest(worker="not self"),\ mock.patch("threading.current_thread") as current_thread: current = mock.Mock() current_thread.return_value = current store.Portfolio.worker = mock.Mock() self.assertFalse(store.Portfolio.is_worker_thread()) with self.subTest(worker="self"),\ mock.patch("threading.current_thread") as current_thread: current = mock.Mock() current_thread.return_value = current store.Portfolio.worker = current self.assertTrue(store.Portfolio.is_worker_thread()) def test_start_worker(self): with mock.patch.object(store.Portfolio, "wait_for_notification") as notification: store.Portfolio.start_worker() notification.assert_called_once_with() self.assertEqual("lock", store.Portfolio.last_date.lock.__class__.__name__) self.assertEqual("lock", store.Portfolio.liquidities.lock.__class__.__name__) store.Portfolio.report.start_lock.assert_called_once_with() self.assertIsNotNone(store.Portfolio.worker) self.assertIsNotNone(store.Portfolio.worker_notify) self.assertIsNotNone(store.Portfolio.callback) self.assertTrue(store.Portfolio.worker_started) self.assertFalse(store.Portfolio.worker.is_alive()) self.assertEqual(1, threading.active_count()) def test_stop_worker(self): with mock.patch.object(store.Portfolio, "get_cryptoportfolio") as get,\ mock.patch.object(store.Portfolio, "report") as report,\ mock.patch.object(store.time, "sleep") as sleep: store.Portfolio.start_worker() store.Portfolio.stop_worker() store.Portfolio.worker.join() get.assert_not_called() report.assert_not_called() sleep.assert_not_called() self.assertFalse(store.Portfolio.worker.is_alive()) def test_wait_for_notification(self): with self.assertRaises(RuntimeError): store.Portfolio.wait_for_notification() with mock.patch.object(store.Portfolio, "get_cryptoportfolio") as get,\ mock.patch.object(store.Portfolio, "report") as report,\ mock.patch.object(store.Portfolio, "next_wait_time") as wait,\ mock.patch.object(store.time, "sleep") as sleep: wait.return_value = 3 store.Portfolio.start_worker() store.Portfolio.worker_notify.set() store.Portfolio.callback.wait() report.print_log.assert_called_once_with("[Worker] Fetching cryptoportfolio") get.assert_called_once_with(refetch=True) sleep.assert_called_once_with(3) self.assertFalse(store.Portfolio.worker_notify.is_set()) self.assertTrue(store.Portfolio.worker.is_alive()) store.Portfolio.callback.clear() store.Portfolio.worker_started = False store.Portfolio.worker_notify.set() store.Portfolio.worker.join() self.assertFalse(store.Portfolio.worker.is_alive()) with self.subTest("overdue"),\ mock.patch.object(store.Portfolio, "get_cryptoportfolio") as get,\ mock.patch.object(store.Portfolio, "report") as report,\ mock.patch.object(store.Portfolio, "next_wait_time") as wait,\ mock.patch.object(store.time, "sleep") as sleep: wait.side_effect = Exception("Time over") store.Portfolio.start_worker() store.Portfolio.worker_notify.set() store.Portfolio.callback.wait() report.print_log.assert_called_once_with("[Worker] Fetching cryptoportfolio") get.assert_called_once_with(refetch=True) self.assertFalse(store.Portfolio.worker.is_alive()) def test_notify_and_wait(self): with mock.patch.object(store.Portfolio, "callback") as callback,\ mock.patch.object(store.Portfolio, "worker_notify") as worker_notify: store.Portfolio.notify_and_wait() callback.clear.assert_called_once_with() worker_notify.set.assert_called_once_with() callback.wait.assert_called_once_with() def test_next_wait_time(self): with self.subTest("first start"): self.assertEqual(30, store.Portfolio.next_wait_time()) self.assertIsNotNone(store.Portfolio.poll_started_at) with self.subTest("25min"): store.Portfolio.poll_started_at = datetime.datetime.now() - datetime.timedelta(minutes=25) self.assertEqual(30, store.Portfolio.next_wait_time()) with self.subTest("35min"): store.Portfolio.poll_started_at = datetime.datetime.now() - datetime.timedelta(minutes=35) self.assertEqual(60, store.Portfolio.next_wait_time()) with self.subTest("1h15"): store.Portfolio.poll_started_at = datetime.datetime.now() - datetime.timedelta(minutes=75) self.assertEqual(300, store.Portfolio.next_wait_time()) with self.subTest("5hours"): store.Portfolio.poll_started_at = datetime.datetime.now() - datetime.timedelta(hours=5) self.assertEqual(3600, store.Portfolio.next_wait_time()) with self.subTest("overdue"), self.assertRaises(Exception): store.Portfolio.poll_started_at = datetime.datetime.now() - datetime.timedelta(hours=25) store.Portfolio.next_wait_time()