def test_private_rest_url(self): path_url = "/TEST_PATH" domain = "com" expected_url = CONSTANTS.REST_URL.format( domain) + CONSTANTS.PRIVATE_API_VERSION + path_url self.assertEqual(expected_url, utils.private_rest_url(path_url, domain))
def test_listen_for_user_stream_handle_ping_frame(self, mock_api, mock_ws): url = utils.private_rest_url( path_url=CONSTANTS.BINANCE_USER_STREAM_PATH_URL, 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)) mock_ws.return_value = self.mocking_assistant.create_websocket_mock() self.mocking_assistant.add_websocket_aiohttp_message( mock_ws.return_value, "", aiohttp.WSMsgType.PING) self.mocking_assistant.add_websocket_aiohttp_message( mock_ws.return_value, self._user_update_event()) msg_queue = asyncio.Queue() self.listening_task = self.ev_loop.create_task( self.data_source.listen_for_user_stream(self.ev_loop, msg_queue)) msg = self.ev_loop.run_until_complete(msg_queue.get()) self.assertTrue(msg, self._user_update_event) self.assertTrue( self._is_logged("DEBUG", "Received PING frame. Sending PONG frame..."))
def test_listen_for_user_stream_iter_message_throws_exception(self, mock_api, mock_ws): url = utils.private_rest_url(path_url=CONSTANTS.BINANCE_USER_STREAM_PATH_URL, 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=json.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 = (lambda *args, **kwargs: self._create_exception_and_unlock_test_with_event( Exception("TEST ERROR"))) mock_ws.close.return_value = None self.listening_task = self.ev_loop.create_task( self.data_source.listen_for_user_stream(self.ev_loop, msg_queue) ) self.async_run_with_timeout(self.resume_test_event.wait()) self.assertTrue( self._is_logged( "ERROR", "Unexpected error while listening to user stream. Retrying after 5 seconds..."))
def test_listen_for_user_stream_connection_failed(self, mock_api, mock_ws): url = utils.private_rest_url( path_url=CONSTANTS.BINANCE_USER_STREAM_PATH_URL, 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)) mock_ws.side_effect = lambda **_: self._create_exception_and_unlock_test_with_event( Exception("TEST ERROR.")) msg_queue = asyncio.Queue() self.listening_task = self.ev_loop.create_task( self.data_source.listen_for_user_stream(self.ev_loop, msg_queue)) self.ev_loop.run_until_complete(self.resume_test_event.wait()) self.assertTrue( self._is_logged( "NETWORK", "Unexpected error occured when connecting to WebSocket server. Error: TEST ERROR." )) self.assertTrue( self._is_logged( "ERROR", "Unexpected error while listening to user stream. Retrying after 5 seconds... Error: TEST ERROR." ))
def test_ping_listen_key_successful(self, mock_api): url = utils.private_rest_url(path_url=CONSTANTS.BINANCE_USER_STREAM_PATH_URL, domain=self.domain) regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) mock_api.put(regex_url, body=json.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_log_exception(self, mock_api): url = utils.private_rest_url(path_url=CONSTANTS.BINANCE_USER_STREAM_PATH_URL, domain=self.domain) regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) mock_api.post(regex_url, status=400, body=json.dumps(self._error_response())) with self.assertRaises(IOError): self.async_run_with_timeout(self.data_source._get_listen_key())
def test_ping_listen_key_log_exception(self, mock_api): url = utils.private_rest_url(path_url=CONSTANTS.BINANCE_USER_STREAM_PATH_URL, domain=self.domain) regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) mock_api.put(regex_url, status=400, body=json.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_get_listen_key_successful(self, mock_api): url = utils.private_rest_url(path_url=CONSTANTS.BINANCE_USER_STREAM_PATH_URL, 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=json.dumps(mock_response)) result: str = self.async_run_with_timeout(self.data_source._get_listen_key()) self.assertEqual(self.listen_key, result)
async def get_listen_key(self): async with aiohttp.ClientSession() as client: async with self._throttler.execute_task( limit_id=CONSTANTS.BINANCE_USER_STREAM_PATH_URL): url = binance_utils.private_rest_url( path_url=CONSTANTS.BINANCE_USER_STREAM_PATH_URL, domain=self._domain) response = await client.post( url=url, headers={"X-MBX-APIKEY": self._binance_client.API_KEY}) if response.status != 200: raise IOError( f"Error fetching user stream listen key. Response: {response}" ) data: Dict[str, str] = await response.json() return data["listenKey"]
async def _api_request(self, method: RESTMethod, path_url: str, params: Optional[Dict[str, Any]] = None, data: Optional[Dict[str, Any]] = None, is_auth_required: bool = False) -> Dict[str, Any]: headers = { "Content-Type": "application/json" if method == RESTMethod.POST else "application/x-www-form-urlencoded" } client = await self._get_rest_assistant() if is_auth_required: url = binance_utils.private_rest_url(path_url, domain=self._domain) else: url = binance_utils.public_rest_url(path_url, domain=self._domain) request = RESTRequest(method=method, url=url, data=data, params=params, headers=headers, is_auth_required=is_auth_required) async with self._throttler.execute_task(limit_id=path_url): response = await client.call(request) if response.status != 200: data = await response.text() raise IOError( f"Error fetching data from {url}. HTTP status is {response.status} ({data})." ) try: parsed_response = await response.json() except Exception: raise IOError(f"Error parsing data from {response}.") if "code" in parsed_response and "msg" in parsed_response: raise IOError( f"The request to Binance failed. Error: {parsed_response}. Request: {request}" ) return parsed_response
async def ping_listen_key(self) -> bool: async with aiohttp.ClientSession() as client: async with self._throttler.execute_task( limit_id=CONSTANTS.BINANCE_USER_STREAM_PATH_URL): url = binance_utils.private_rest_url( path_url=CONSTANTS.BINANCE_USER_STREAM_PATH_URL, domain=self._domain) response = await client.put( url=url, headers={"X-MBX-APIKEY": self._binance_client.API_KEY}, params={"listenKey": self._current_listen_key}) data: Tuple[str, any] = await response.json() if "code" in data: self.logger().warning( f"Failed to refresh the listen key {self._current_listen_key}: {data}" ) return False return True
async def _get_listen_key(self): rest_assistant = await self._get_rest_assistant() url = binance_utils.private_rest_url( path_url=CONSTANTS.BINANCE_USER_STREAM_PATH_URL, domain=self._domain) request = RESTRequest(method=RESTMethod.POST, url=url, headers=self._auth.header_for_authentication()) async with self._throttler.execute_task( limit_id=CONSTANTS.BINANCE_USER_STREAM_PATH_URL): response: RESTResponse = await rest_assistant.call(request=request) if response.status != 200: raise IOError( f"Error fetching user stream listen key. Response: {response}" ) data: Dict[str, str] = await response.json() return data["listenKey"]
def test_listen_for_user_stream_get_listen_key_successful_with_user_update_event(self, mock_api, mock_ws): url = utils.private_rest_url(path_url=CONSTANTS.BINANCE_USER_STREAM_PATH_URL, 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=json.dumps(mock_response)) mock_ws.return_value = self.mocking_assistant.create_websocket_mock() self.mocking_assistant.add_websocket_aiohttp_message(mock_ws.return_value, self._user_update_event()) msg_queue = asyncio.Queue() self.listening_task = self.ev_loop.create_task( self.data_source.listen_for_user_stream(self.ev_loop, msg_queue) ) msg = self.async_run_with_timeout(msg_queue.get()) self.assertTrue(msg, self._user_update_event)
def test_listen_for_user_stream_does_not_queue_empty_payload(self, mock_api, mock_ws): url = utils.private_rest_url(path_url=CONSTANTS.BINANCE_USER_STREAM_PATH_URL, 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=json.dumps(mock_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(self.ev_loop, msg_queue) ) self.mocking_assistant.run_until_all_aiohttp_messages_delivered(mock_ws.return_value) self.assertEqual(0, msg_queue.qsize())
async def _ping_listen_key(self) -> bool: rest_assistant = await self._get_rest_assistant() url = binance_utils.private_rest_url( path_url=CONSTANTS.BINANCE_USER_STREAM_PATH_URL, domain=self._domain) request = RESTRequest(method=RESTMethod.PUT, url=url, headers=self._auth.header_for_authentication(), params={"listenKey": self._current_listen_key}) async with self._throttler.execute_task( limit_id=CONSTANTS.BINANCE_USER_STREAM_PATH_URL): response: RESTResponse = await rest_assistant.call(request=request) data: Tuple[str, any] = await response.json() if "code" in data: self.logger().warning( f"Failed to refresh the listen key {self._current_listen_key}: {data}" ) return False return True