def test_get_last_traded_prices(self, mock_api): url = web_utils.rest_url(CONSTANTS.SERVER_TIME_PATH_URL, domain=self.domain) regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) response = {"serverTime": 1640000003000} mock_api.get(regex_url, body=json.dumps(response)) url = web_utils.rest_url(path_url=CONSTANTS.TICKER_PRICE_CHANGE_URL, domain=self.domain) regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) mock_response: Dict[str, Any] = { # Truncated responses "lastPrice": "10.0", } mock_api.get(regex_url, body=json.dumps(mock_response)) result: Dict[str, Any] = self.async_run_with_timeout( self.data_source.get_last_traded_prices( trading_pairs=[self.trading_pair], domain=self.domain)) self.assertTrue(self.trading_pair in result) self.assertEqual(10.0, result[self.trading_pair])
def test_rest_url_main_domain(self): path_url = "/TEST_PATH_URL" expected_url = f"{CONSTANTS.PERPETUAL_BASE_URL}{CONSTANTS.API_VERSION_V2}{path_url}" self.assertEqual( expected_url, web_utils.rest_url(path_url, api_version=CONSTANTS.API_VERSION_V2)) self.assertEqual( expected_url, web_utils.rest_url(path_url, api_version=CONSTANTS.API_VERSION_V2))
def test_listen_for_order_book_snapshots_logs_exception_error_with_response( self, mock_api): url = web_utils.rest_url(CONSTANTS.SNAPSHOT_REST_URL, domain=self.domain) regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) mock_response = { "m": 1, "i": 2, } mock_api.get(regex_url, body=json.dumps(mock_response), callback=self.resume_test_callback) msg_queue: asyncio.Queue = asyncio.Queue() self.listening_task = self.ev_loop.create_task( self.data_source.listen_for_order_book_snapshots( self.ev_loop, msg_queue)) self.async_run_with_timeout(self.resume_test_event.wait()) self.assertTrue( self._is_logged( "ERROR", "Unexpected error occurred fetching orderbook snapshots. Retrying in 5 seconds..." ))
def test_get_funding_info(self, mock_api): self.assertNotIn(self.trading_pair, self.data_source._funding_info) url = web_utils.rest_url(CONSTANTS.MARK_PRICE_URL, domain=self.domain) regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) mock_response = { "symbol": self.ex_trading_pair, "markPrice": "46382.32704603", "indexPrice": "46385.80064948", "estimatedSettlePrice": "46510.13598963", "lastFundingRate": "0.00010000", "interestRate": "0.00010000", "nextFundingTime": 1641312000000, "time": 1641288825000, } mock_api.get(regex_url, body=json.dumps(mock_response)) result = self.async_run_with_timeout( self.data_source.get_funding_info(trading_pair=self.trading_pair)) self.assertIsInstance(result, FundingInfo) self.assertEqual(result.trading_pair, self.trading_pair) self.assertEqual(result.index_price, Decimal(mock_response["indexPrice"])) self.assertEqual(result.mark_price, Decimal(mock_response["markPrice"])) self.assertEqual(result.next_funding_utc_timestamp, mock_response["nextFundingTime"]) self.assertEqual(result.rate, Decimal(mock_response["lastFundingRate"]))
def test_init_trading_pair_symbols_successful(self, mock_api): url = web_utils.rest_url(path_url=CONSTANTS.EXCHANGE_INFO_URL, domain=self.domain) regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) mock_response: Dict[str, Any] = { # Truncated Responses "symbols": [ { "symbol": self.ex_trading_pair, "pair": self.ex_trading_pair, "baseAsset": self.base_asset, "quoteAsset": self.quote_asset, "status": "TRADING", }, { "symbol": "INACTIVEMARKET", "status": "INACTIVE" }, ], } mock_api.get(regex_url, status=200, body=json.dumps(mock_response)) self.async_run_with_timeout( self.data_source.init_trading_pair_symbols( domain=self.domain, time_synchronizer=self.data_source._time_synchronizer)) self.assertEqual(1, len(self.data_source._trading_pair_symbol_map))
def test_manage_listen_key_task_loop_keep_alive_successful(self, mock_api): url = web_utils.rest_url( path_url=CONSTANTS.BINANCE_USER_STREAM_ENDPOINT, domain=self.domain) regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) mock_api.put(regex_url, body=ujson.dumps({}), callback=self._mock_responses_done_callback) self.data_source._current_listen_key = self.listen_key # Simulate LISTEN_KEY_KEEP_ALIVE_INTERVAL reached self.data_source._last_listen_key_ping_ts = 0 self.listening_task = self.ev_loop.create_task( self.data_source._manage_listen_key_task_loop()) self.async_run_with_timeout(self.mock_done_event.wait()) self.assertTrue( self._is_logged("INFO", f"Refreshed listen key {self.listen_key}.")) self.assertGreater(self.data_source._last_listen_key_ping_ts, 0)
def test_manage_listen_key_task_loop_keep_alive_failed(self, mock_api): url = web_utils.rest_url( path_url=CONSTANTS.BINANCE_USER_STREAM_ENDPOINT, domain=self.domain) regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) mock_api.put(regex_url, status=400, body=ujson.dumps(self._error_response()), callback=self._mock_responses_done_callback) self.data_source._current_listen_key = self.listen_key # Simulate LISTEN_KEY_KEEP_ALIVE_INTERVAL reached self.data_source._last_listen_key_ping_ts = 0 self.listening_task = self.ev_loop.create_task( self.data_source._manage_listen_key_task_loop()) self.async_run_with_timeout(self.mock_done_event.wait()) self.assertTrue( self._is_logged("ERROR", "Error occurred renewing listen key... ")) self.assertIsNone(self.data_source._current_listen_key) self.assertFalse( self.data_source._listen_key_initialized_event.is_set())
def test_listen_for_user_stream_create_websocket_connection_failed( self, mock_api, mock_ws): url = web_utils.rest_url( path_url=CONSTANTS.BINANCE_USER_STREAM_ENDPOINT, domain=self.domain) regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) mock_api.post(regex_url, body=self._successful_get_listen_key_response()) mock_ws.side_effect = Exception("TEST ERROR.") msg_queue = asyncio.Queue() self.listening_task = self.ev_loop.create_task( self.data_source.listen_for_user_stream(msg_queue)) try: self.async_run_with_timeout(msg_queue.get()) except asyncio.exceptions.TimeoutError: pass self.assertTrue( self._is_logged( "INFO", f"Successfully obtained listen key {self.listen_key}")) self.assertTrue( self._is_logged( "ERROR", "Unexpected error while listening to user stream. Retrying after 5 seconds... Error: TEST ERROR.", ))
def test_listen_for_user_stream_iter_message_throws_exception( self, mock_api, _, mock_ws): url = web_utils.rest_url( path_url=CONSTANTS.BINANCE_USER_STREAM_ENDPOINT, domain=self.domain) regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) mock_response = {"listenKey": self.listen_key} mock_api.post(regex_url, body=ujson.dumps(mock_response)) msg_queue: asyncio.Queue = asyncio.Queue() mock_ws.return_value = self.mocking_assistant.create_websocket_mock() mock_ws.return_value.receive.side_effect = Exception("TEST ERROR") mock_ws.return_value.closed = False mock_ws.return_value.close.side_effect = Exception self.listening_task = self.ev_loop.create_task( self.data_source.listen_for_user_stream(msg_queue)) try: self.async_run_with_timeout(msg_queue.get()) except Exception: pass self.assertTrue( self._is_logged( "INFO", f"Successfully obtained listen key {self.listen_key}")) self.assertTrue( self._is_logged( "ERROR", "Unexpected error while listening to user stream. Retrying after 5 seconds... Error: TEST ERROR", ))
def test_trading_pair_symbol_map_dictionary_not_initialized( self, mock_api): BinancePerpetualAPIOrderBookDataSource._trading_pair_symbol_map = {} url = web_utils.rest_url(path_url=CONSTANTS.EXCHANGE_INFO_URL, domain=self.domain) regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) mock_response: Dict[str, Any] = { # Truncated Responses "symbols": [ { "symbol": self.ex_trading_pair, "pair": self.ex_trading_pair, "baseAsset": self.base_asset, "quoteAsset": self.quote_asset, "status": "TRADING", }, ] } mock_api.get(regex_url, status=200, body=json.dumps(mock_response)) self.async_run_with_timeout( self.data_source.trading_pair_symbol_map( domain=self.domain, time_synchronizer=self.data_source._time_synchronizer)) self.assertEqual(1, len(self.data_source._trading_pair_symbol_map))
def test_listen_for_order_book_snapshots_successful(self, mock_api): url = web_utils.rest_url(CONSTANTS.SNAPSHOT_REST_URL, domain=self.domain) regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) mock_response = { "lastUpdateId": 1027024, "E": 1589436922972, "T": 1589436922959, "bids": [["10", "1"]], "asks": [["11", "1"]], } mock_api.get(regex_url, body=json.dumps(mock_response)) msg_queue: asyncio.Queue = asyncio.Queue() self.listening_task = self.ev_loop.create_task( self.data_source.listen_for_order_book_snapshots( self.ev_loop, msg_queue)) result = self.async_run_with_timeout(msg_queue.get()) self.assertIsInstance(result, OrderBookMessage) self.assertEqual(OrderBookMessageType.SNAPSHOT, result.type) self.assertTrue(result.has_update_id) self.assertEqual(result.update_id, 1027024) self.assertEqual(self.trading_pair, result.content["trading_pair"])
def test_rest_url_testnet_domain(self): path_url = "/TEST_PATH_URL" expected_url = f"{CONSTANTS.TESTNET_BASE_URL}{CONSTANTS.API_VERSION_V2}{path_url}" self.assertEqual( expected_url, web_utils.rest_url(path_url=path_url, domain="testnet", api_version=CONSTANTS.API_VERSION_V2))
def test_get_listen_key_exception_raised(self, mock_api): url = web_utils.rest_url( path_url=CONSTANTS.BINANCE_USER_STREAM_ENDPOINT, domain=self.domain) regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) mock_api.post(regex_url, status=400, body=ujson.dumps(self._error_response)) with self.assertRaises(IOError): self.async_run_with_timeout(self.data_source.get_listen_key())
def test_ping_listen_key_successful(self, mock_api): url = web_utils.rest_url( path_url=CONSTANTS.BINANCE_USER_STREAM_ENDPOINT, domain=self.domain) regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) mock_api.put(regex_url, body=ujson.dumps({})) self.data_source._current_listen_key = self.listen_key result: bool = self.async_run_with_timeout( self.data_source.ping_listen_key()) self.assertTrue(result)
def test_get_listen_key_successful(self, mock_api): url = web_utils.rest_url( path_url=CONSTANTS.BINANCE_USER_STREAM_ENDPOINT, domain=self.domain) regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) mock_api.post(regex_url, body=self._successful_get_listen_key_response()) result: str = self.async_run_with_timeout( self.data_source.get_listen_key()) self.assertEqual(self.listen_key, result)
def test_init_trading_pair_symbols_failure(self, mock_api): BinancePerpetualAPIOrderBookDataSource._trading_pair_symbol_map = {} url = web_utils.rest_url(path_url=CONSTANTS.EXCHANGE_INFO_URL, domain=self.domain) regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) mock_api.get(regex_url, status=400, body=json.dumps(["ERROR"])) map = self.async_run_with_timeout( self.data_source.trading_pair_symbol_map( domain=self.domain, time_synchronizer=self.data_source._time_synchronizer)) self.assertEqual(0, len(map))
def test_get_funding_info_from_exchange_error_response(self, mock_api): url = web_utils.rest_url(CONSTANTS.MARK_PRICE_URL, domain=self.domain) regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) mock_api.get(regex_url, status=400) result = self.async_run_with_timeout( self.data_source._get_funding_info_from_exchange( self.trading_pair)) self.assertIsNone(result) self._is_logged( "ERROR", f"Unable to fetch FundingInfo for {self.trading_pair}. Error: None" )
def test_get_snapshot_exception_raised(self, mock_api): url = web_utils.rest_url(CONSTANTS.SNAPSHOT_REST_URL, domain=self.domain) regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) mock_api.get(regex_url, status=400, body=json.dumps(["ERROR"])) with self.assertRaises(IOError) as context: self.async_run_with_timeout( self.data_source.get_snapshot( trading_pair=self.trading_pair, domain=self.domain, time_synchronizer=self.data_source._time_synchronizer)) self.assertEqual( "Error executing request GET /depth. HTTP status is 400. Error: [\"ERROR\"]", str(context.exception))
def test_get_new_order_book(self, mock_api): url = web_utils.rest_url(CONSTANTS.SNAPSHOT_REST_URL, domain=self.domain) regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) mock_response = { "lastUpdateId": 1027024, "E": 1589436922972, "T": 1589436922959, "bids": [["10", "1"]], "asks": [["11", "1"]], } mock_api.get(regex_url, status=200, body=json.dumps(mock_response)) result = self.async_run_with_timeout( self.data_source.get_new_order_book( trading_pair=self.trading_pair)) self.assertIsInstance(result, OrderBook) self.assertEqual(1027024, result.snapshot_uid)
def test_listen_for_order_book_snapshots_cancelled_error_raised( self, mock_api): url = web_utils.rest_url(CONSTANTS.SNAPSHOT_REST_URL, domain=self.domain) regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) mock_api.get(regex_url, exception=asyncio.CancelledError) msg_queue: asyncio.Queue = asyncio.Queue() with self.assertRaises(asyncio.CancelledError): self.listening_task = self.ev_loop.create_task( self.data_source.listen_for_order_book_snapshots( self.ev_loop, msg_queue)) self.async_run_with_timeout(self.listening_task) self.assertEqual(0, msg_queue.qsize())
def test_get_snapshot_successful(self, mock_api): url = web_utils.rest_url(CONSTANTS.SNAPSHOT_REST_URL, domain=self.domain) regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) mock_response = { "lastUpdateId": 1027024, "E": 1589436922972, "T": 1589436922959, "bids": [["10", "1"]], "asks": [["11", "1"]], } mock_api.get(regex_url, status=200, body=json.dumps(mock_response)) result: Dict[str, Any] = self.async_run_with_timeout( self.data_source.get_snapshot( trading_pair=self.trading_pair, domain=self.domain, time_synchronizer=self.data_source._time_synchronizer)) self.assertEqual(mock_response, result)
def test_ping_listen_key_failed_log_warning(self, mock_api): url = web_utils.rest_url( path_url=CONSTANTS.BINANCE_USER_STREAM_ENDPOINT, domain=self.domain) regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) mock_api.put(regex_url, status=400, body=ujson.dumps(self._error_response())) self.data_source._current_listen_key = self.listen_key result: bool = self.async_run_with_timeout( self.data_source.ping_listen_key()) self.assertTrue( self._is_logged( "WARNING", f"Failed to refresh the listen key {self.listen_key}: {self._error_response()}" )) self.assertFalse(result)
def test_listen_for_user_stream_successful(self, mock_api, mock_ws): url = web_utils.rest_url( path_url=CONSTANTS.BINANCE_USER_STREAM_ENDPOINT, domain=self.domain) regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) mock_api.post(regex_url, body=self._successful_get_listen_key_response()) mock_ws.return_value = self.mocking_assistant.create_websocket_mock() self.mocking_assistant.add_websocket_aiohttp_message( mock_ws.return_value, self._simulate_user_update_event()) msg_queue = asyncio.Queue() self.listening_task = self.ev_loop.create_task( self.data_source.listen_for_user_stream(msg_queue)) msg = self.async_run_with_timeout(msg_queue.get()) self.assertTrue(msg, self._simulate_user_update_event) mock_ws.return_value.ping.assert_called()
def test_listen_for_user_stream_does_not_queue_empty_payload( self, mock_api, mock_ws): url = web_utils.rest_url( path_url=CONSTANTS.BINANCE_USER_STREAM_ENDPOINT, domain=self.domain) regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) mock_api.post(regex_url, body=self._successful_get_listen_key_response()) mock_ws.return_value = self.mocking_assistant.create_websocket_mock() self.mocking_assistant.add_websocket_aiohttp_message( mock_ws.return_value, "") msg_queue = asyncio.Queue() self.listening_task = self.ev_loop.create_task( self.data_source.listen_for_user_stream(msg_queue)) self.mocking_assistant.run_until_all_aiohttp_messages_delivered( mock_ws.return_value) self.assertEqual(0, msg_queue.qsize())