+@unittest.skipUnless("unit" in limits, "Unit skipped")
+class LockedVar(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])
+
+ 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.time, "sleep") as sleep:
+ store.Portfolio.start_worker(poll=3)
+
+ store.Portfolio.worker_notify.set()
+
+ store.Portfolio.callback.wait()
+
+ report.print_log.assert_called_once_with("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.callback.wait()
+
+ 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()
+
+@unittest.skipUnless("unit" in limits, "Unit skipped")
+class PortfolioTest(WebMockTestCase):
+ def setUp(self):
+ super(PortfolioTest, self).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")
+ def test_get_cryptoportfolio(self, 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()
+ market.Portfolio.report.log_http_request.reset_mock()
+ parse_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()
+ 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()
+
+ market.Portfolio.data = store.LockedVar("Foo")
+ market.Portfolio.get_cryptoportfolio()
+ self.assertEqual(2, self.wm.call_count)
+ parse_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()
+ with self.subTest(worker=False):
+ market.Portfolio.data = store.LockedVar(None)
+ market.Portfolio.worker = mock.Mock()
+ is_worker.return_value = False
+ market.Portfolio.get_cryptoportfolio()
+ notify.assert_called_once_with()
+ parse_cryptoportfolio.assert_not_called()
+
+ 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(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(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)
+
+ market.Portfolio.parse_cryptoportfolio()
+ self.assertListEqual(
+ ["medium", "high"],
+ list(market.Portfolio.liquidities.get().keys()))
+ self.assertEqual({}, market.Portfolio.liquidities.get("medium"))
+
+ with self.subTest(description="All missing weights"):
+ data = store.json.loads(self.json_response, parse_int=D, parse_float=D)
+ del(data["portfolio_1"]["weights"])
+ del(data["portfolio_2"]["weights"])
+ market.Portfolio.data = store.LockedVar(data)
+
+ market.Portfolio.parse_cryptoportfolio()
+ self.assertEqual({}, market.Portfolio.liquidities.get("medium"))
+ self.assertEqual({}, market.Portfolio.liquidities.get("high"))
+ self.assertEqual(datetime.datetime(1,1,1), market.Portfolio.last_date.get())
+
+
+ @mock.patch.object(market.Portfolio, "get_cryptoportfolio")
+ def test_repartition(self, get_cryptoportfolio):
+ 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()
+ self.assertEqual("medium_2018-03-08", market.Portfolio.repartition(liquidity="medium"))
+ self.assertEqual("high_2018-03-08", market.Portfolio.repartition(liquidity="high"))
+
+ @mock.patch.object(market.time, "sleep")
+ @mock.patch.object(market.Portfolio, "get_cryptoportfolio")
+ def test_wait_for_recent(self, get_cryptoportfolio, sleep):