From e3e96748e7ac3eac2e98edf5bc64168d889a32fc Mon Sep 17 00:00:00 2001 From: Pablo Date: Fri, 20 Feb 2026 03:33:42 +0100 Subject: [PATCH] ci: split tests into offline and live with pytest markers Separate mocked/offline tests from live/testnet tests using a `@pytest.mark.live` marker so offline tests run strict (no reruns) and live tests tolerate flakiness (--reruns 3). - Register `live` marker in conftest.py and pyproject.toml - Add `pytest.mark.live` to 23 live test files - Handle mixed file (test_client_gift_card.py) with per-function markers - Refactor module-level API call in test_threaded_socket_manager.py into fixture - Add tox environments: `offline` and `live` - Split GitHub Actions into parallel test-offline and test-live jobs --- .github/workflows/python-app.yml | 44 ++++- pyproject.toml | 10 +- tests/conftest.py | 44 +++-- tests/test_async_client.py | 162 +++++++++++------- tests/test_async_client_futures.py | 129 +++++++++++++- tests/test_async_client_gift_card copy.py | 2 +- tests/test_async_client_margin.py | 145 +++++++++++++++- tests/test_async_client_options.py | 35 +++- tests/test_async_client_portfolio.py | 2 +- tests/test_async_client_ws_api.py | 24 ++- .../test_async_client_ws_futures_requests.py | 35 +++- tests/test_client.py | 59 ++++--- tests/test_client_futures.py | 9 +- tests/test_client_gift_card.py | 4 + tests/test_client_margin.py | 17 +- tests/test_client_options.py | 8 +- tests/test_client_portfolio.py | 2 +- tests/test_client_ws_api.py | 8 +- tests/test_client_ws_futures_requests.py | 2 + tests/test_get_order_book.py | 2 + tests/test_ping.py | 2 + tests/test_socket_manager.py | 2 + tests/test_streams.py | 3 + tests/test_streams_options.py | 42 +++-- tests/test_threaded_socket_manager.py | 134 +++++++++------ tests/test_user_socket_integration.py | 48 ++++-- tests/test_ws_api.py | 22 ++- tox.ini | 19 ++ 28 files changed, 784 insertions(+), 231 deletions(-) diff --git a/.github/workflows/python-app.yml b/.github/workflows/python-app.yml index 153c9baba..b5cdbf8de 100644 --- a/.github/workflows/python-app.yml +++ b/.github/workflows/python-app.yml @@ -31,7 +31,38 @@ jobs: run: ruff format --check . continue-on-error: true - test: + test-offline: + needs: lint + runs-on: ubuntu-22.04 + timeout-minutes: 10 + strategy: + matrix: + python-version: ['3.8', '3.9', '3.10', '3.11', '3.12'] + steps: + - uses: actions/checkout@v5 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v6 + with: + python-version: ${{ matrix.python-version }} + cache: 'pip' + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install pytest pytest-cov pyright tox + if [ -f requirements.txt ]; then pip install -r requirements.txt; fi + if [ -f test-requirements.txt ]; then pip install -r test-requirements.txt; fi + - name: Type check with pyright (Python 3.12 only) + if: matrix.python-version == '3.12' + run: pyright + - name: Test offline (mocked/unit tests) + run: tox -e offline + - name: Coveralls Parallel + uses: coverallsapp/github-action@v2 + with: + parallel: true + flag-name: offline-py${{ matrix.python-version }} + + test-live: needs: lint runs-on: ubuntu-22.04 timeout-minutes: 40 @@ -63,17 +94,16 @@ jobs: pip install pytest pytest-cov pyright tox if [ -f requirements.txt ]; then pip install -r requirements.txt; fi if [ -f test-requirements.txt ]; then pip install -r test-requirements.txt; fi - - name: Type check with pyright (Python 3.12 only) - if: matrix.python-version == '3.12' - run: pyright - - name: Test with tox - run: tox -e py + - name: Test live (network/testnet tests) + run: tox -e live - name: Coveralls Parallel uses: coverallsapp/github-action@v2 with: parallel: true + flag-name: live-py${{ matrix.python-version }} + finish: - needs: test + needs: [test-offline, test-live] if: ${{ always() }} runs-on: ubuntu-latest timeout-minutes: 5 diff --git a/pyproject.toml b/pyproject.toml index b69cc7ed3..ffcf38120 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,4 +6,12 @@ lint.ignore = ["F722","F841","F821","E402","E501","E902","E713","E741","E714", " timeout = 90 timeout_method = "thread" asyncio_default_fixture_loop_scope = "function" - +markers = [ + "live: requires network access to Binance", + "spot: mark a test as part of the spot tests", + "futures: mark a test as part of the futures tests", + "margin: mark a test as part of the margin tests", + "portfolio: mark a test as part of the portfolio tests", + "gift_card: mark a test as part of the gift card tests", + "options: mark a test as part of the options tests", +] diff --git a/tests/conftest.py b/tests/conftest.py index d0537e197..8fdb7befc 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -24,8 +24,8 @@ testnet = os.getenv("TEST_TESTNET", "true").lower() == "true" api_key = "u4L8MG2DbshTfTzkx2Xm7NfsHHigvafxeC29HrExEmah1P8JhxXkoOu6KntLICUc" api_secret = "hBZEqhZUUS6YZkk7AIckjJ3iLjrgEFr5CRtFPp5gjzkrHKKC9DAv4OH25PlT6yq5" -testnet = True # only for spot now -demo = True # spot and swap +testnet = True # only for spot now +demo = True # spot and swap futures_api_key = "HjhMFvuF1veWQVdUbLIy7TiCYe9fj4W6sEukmddD8TM9kPVRHMK6nS2SdV5mwE5u" futures_api_secret = "Suu9pWcO9zbvVuc6cSQsVuiiw2DmmA8DgHrUfePF9s2RtaHa0zxK3eAF4MfIk7Pd" @@ -58,9 +58,7 @@ def liveClient(): @pytest.fixture(scope="function") def futuresClient(): - return Client( - futures_api_key, futures_api_secret, {"proxies": proxies}, demo=demo - ) + return Client(futures_api_key, futures_api_secret, {"proxies": proxies}, demo=demo) @pytest_asyncio.fixture(scope="function") @@ -91,12 +89,14 @@ async def liveClientAsync(): finally: await client.close_connection() + @pytest.fixture(scope="function") def manager(): return ThreadedWebsocketManager( api_key="test_key", api_secret="test_secret", https_proxy=proxy, testnet=True ) + @pytest.fixture(autouse=True, scope="function") def event_loop(): """Create new event loop for each test""" @@ -111,7 +111,9 @@ def event_loop(): for task in pending: task.cancel() if pending: - loop.run_until_complete(asyncio.gather(*pending, return_exceptions=True)) + loop.run_until_complete( + asyncio.gather(*pending, return_exceptions=True) + ) except Exception: pass # Ignore cleanup errors finally: @@ -157,6 +159,10 @@ def pytest_configure(config): config.addinivalue_line( "markers", "gift_card: mark a test as part of the gift card tests" ) + config.addinivalue_line( + "markers", "options: mark a test as part of the options tests" + ) + config.addinivalue_line("markers", "live: requires network access to Binance") def pytest_collection_modifyitems(config, items): @@ -178,36 +184,40 @@ def pytest_collection_modifyitems(config, items): item.add_marker(skip_gift_card) -def call_method_and_assert_uri_contains(client, method_name, expected_string, *args, **kwargs): +def call_method_and_assert_uri_contains( + client, method_name, expected_string, *args, **kwargs +): """ Helper function to test that a client method calls the expected URI. - + Args: client: The client instance to test method_name: Name of the method to call (as string) expected_string: String that should be present in the URI *args, **kwargs: Arguments to pass to the client method - + Returns: The result of the method call """ from unittest.mock import patch - - with patch.object(client, '_request', wraps=client._request) as mock_request: + + with patch.object(client, "_request", wraps=client._request) as mock_request: # Get the method from the client and call it method = getattr(client, method_name) result = method(*args, **kwargs) - + # Assert that _request was called mock_request.assert_called_once() - + # Get the arguments passed to _request args_passed, kwargs_passed = mock_request.call_args - + # The second argument is the URI uri = args_passed[1] - + # Assert that the URL contains the expected string - assert expected_string in uri, f"Expected '{expected_string}' in URL, but got: {uri}" - + assert expected_string in uri, ( + f"Expected '{expected_string}' in URL, but got: {uri}" + ) + return result diff --git a/tests/test_async_client.py b/tests/test_async_client.py index ee3563b04..0ae16e71a 100644 --- a/tests/test_async_client.py +++ b/tests/test_async_client.py @@ -8,7 +8,7 @@ from aiohttp.helpers import TimerNoop from yarl import URL -pytestmark = [pytest.mark.asyncio] +pytestmark = [pytest.mark.asyncio, pytest.mark.live] async def test_clientAsync_initialization(clientAsync): @@ -19,94 +19,117 @@ async def test_clientAsync_initialization(clientAsync): @pytest.mark.skip(reason="Endpoint not documented") async def test_get_products(clientAsync): await clientAsync.get_products() - + async def test_get_exchange_info(clientAsync): await clientAsync.get_exchange_info() - + + async def test_get_symbol_info(clientAsync): await clientAsync.get_symbol_info("BTCUSDT") - + async def test_ping(clientAsync): await clientAsync.ping() - + + async def test_get_server_time(clientAsync): await clientAsync.get_server_time() - + + async def test_get_all_tickers(clientAsync): await clientAsync.get_all_tickers() + async def test_get_orderbook_tickers(clientAsync): await clientAsync.get_orderbook_tickers() - + + async def test_get_order_book(clientAsync): await clientAsync.get_order_book(symbol="BTCUSDT") - + + async def test_get_recent_trades(clientAsync): await clientAsync.get_recent_trades(symbol="BTCUSDT") async def test_get_historical_trades(clientAsync): await clientAsync.get_historical_trades(symbol="BTCUSDT") - + async def test_get_aggregate_trades(clientAsync): await clientAsync.get_aggregate_trades(symbol="BTCUSDT") - + + async def test_get_klines(clientAsync): await clientAsync.get_klines(symbol="BTCUSDT", interval="1d") - + + async def test_get_uiklines(clientAsync): await clientAsync.get_ui_klines(symbol="BTCUSDT", interval="1d") - + + async def test_futures_mark_price_klines(clientAsync): await clientAsync.futures_mark_price_klines(symbol="BTCUSDT", interval="1h") - + + async def test_futures_index_price_klines(clientAsync): await clientAsync.futures_index_price_klines(pair="BTCUSDT", interval="1h") - + + async def test_futures_premium_index_klines(clientAsync): await clientAsync.futures_premium_index_klines(symbol="BTCUSDT", interval="1h") - + + @pytest.mark.skip(reason="network error") async def test_futures_coin_premium_index_klines(clientAsync): await clientAsync.futures_coin_premium_index_klines(symbol="BTCUSD", interval="1h") - + + async def test_get_avg_price(clientAsync): await clientAsync.get_avg_price(symbol="BTCUSDT") - + + async def test_get_ticker(clientAsync): await clientAsync.get_ticker(symbol="BTCUSDT") - + + async def test_get_symbol_ticker(clientAsync): await clientAsync.get_symbol_ticker(symbol="BTCUSDT") - + + async def test_get_orderbook_ticker(clientAsync): await clientAsync.get_orderbook_ticker(symbol="BTCUSDT") - + + async def test_get_account(clientAsync): await clientAsync.get_account() - + + async def test_get_asset_balance(clientAsync): await clientAsync.get_asset_balance(asset="BTC") - + + async def test_get_asset_balance_no_asset_provided(clientAsync): await clientAsync.get_asset_balance() - + + async def test_get_my_trades(clientAsync): await clientAsync.get_my_trades(symbol="BTCUSDT") - + + async def test_get_system_status(clientAsync): await clientAsync.get_system_status() - + + # User Stream Endpoints async def test_stream_get_listen_key_and_close(clientAsync): listen_key = await clientAsync.stream_get_listen_key() await clientAsync.stream_close(listen_key) - + + # Quoting interface endpoints @@ -118,105 +141,119 @@ async def test_stream_get_listen_key_and_close(clientAsync): @pytest.mark.skipif(sys.version_info < (3, 8), reason="websockets_proxy Python 3.8+") async def test_ws_get_order_book(clientAsync): await clientAsync.ws_get_order_book(symbol="BTCUSDT") - + + @pytest.mark.skipif(sys.version_info < (3, 8), reason="websockets_proxy Python 3.8+") async def test_ws_get_recent_trades(clientAsync): await clientAsync.ws_get_recent_trades(symbol="BTCUSDT") - + + @pytest.mark.skipif(sys.version_info < (3, 8), reason="websockets_proxy Python 3.8+") async def test_ws_get_historical_trades(clientAsync): await clientAsync.ws_get_historical_trades(symbol="BTCUSDT") - + + @pytest.mark.skipif(sys.version_info < (3, 8), reason="websockets_proxy Python 3.8+") async def test_ws_get_aggregate_trades(clientAsync): await clientAsync.ws_get_aggregate_trades(symbol="BTCUSDT") - + + @pytest.mark.skipif(sys.version_info < (3, 8), reason="websockets_proxy Python 3.8+") async def test_ws_get_klines(clientAsync): await clientAsync.ws_get_klines(symbol="BTCUSDT", interval="1m") - + + @pytest.mark.skipif(sys.version_info < (3, 8), reason="websockets_proxy Python 3.8+") async def test_ws_get_uiKlines(clientAsync): await clientAsync.ws_get_uiKlines(symbol="BTCUSDT", interval="1m") - + + @pytest.mark.skipif(sys.version_info < (3, 8), reason="websockets_proxy Python 3.8+") async def test_ws_get_avg_price(clientAsync): await clientAsync.ws_get_avg_price(symbol="BTCUSDT") - + + @pytest.mark.skipif(sys.version_info < (3, 8), reason="websockets_proxy Python 3.8+") async def test_ws_get_ticker(clientAsync): ticker = await clientAsync.ws_get_ticker(symbol="BTCUSDT") - + + @pytest.mark.skipif(sys.version_info < (3, 8), reason="websockets_proxy Python 3.8+") async def test_ws_get_trading_day_ticker(clientAsync): await clientAsync.ws_get_trading_day_ticker(symbol="BTCUSDT") - + + @pytest.mark.skipif(sys.version_info < (3, 8), reason="websockets_proxy Python 3.8+") async def test_ws_get_symbol_ticker_window(clientAsync): await clientAsync.ws_get_symbol_ticker_window(symbol="BTCUSDT") - + + @pytest.mark.skipif(sys.version_info < (3, 8), reason="websockets_proxy Python 3.8+") async def test_ws_get_symbol_ticker(clientAsync): await clientAsync.ws_get_symbol_ticker(symbol="BTCUSDT") - + + @pytest.mark.skipif(sys.version_info < (3, 8), reason="websockets_proxy Python 3.8+") async def test_ws_get_orderbook_ticker(clientAsync): await clientAsync.ws_get_orderbook_ticker(symbol="BTCUSDT") - + + @pytest.mark.skipif(sys.version_info < (3, 8), reason="websockets_proxy Python 3.8+") async def test_ws_ping(clientAsync): await clientAsync.ws_ping() - + + @pytest.mark.skipif(sys.version_info < (3, 8), reason="websockets_proxy Python 3.8+") async def test_ws_get_time(clientAsync): await clientAsync.ws_get_time() - + + @pytest.mark.skipif(sys.version_info < (3, 8), reason="websockets_proxy Python 3.8+") async def test_ws_get_exchange_info(clientAsync): await clientAsync.ws_get_exchange_info(symbol="BTCUSDT") - + + @pytest.mark.skip(reason="can't test margin endpoints") async def test_margin_next_hourly_interest_rate(clientAsync): - await clientAsync.margin_next_hourly_interest_rate( - assets="BTC", - isIsolated="FALSE" - ) - + await clientAsync.margin_next_hourly_interest_rate(assets="BTC", isIsolated="FALSE") + + @pytest.mark.skip(reason="can't test margin endpoints") async def test_margin_interest_history(clientAsync): await clientAsync.margin_interest_history( asset="BTC", ) - + + @pytest.mark.skip(reason="can't test margin endpoints") async def test_margin_borrow_repay(clientAsync): await clientAsync.margin_borrow_repay( - asset="BTC", - amount=0.1, - isIsolated="FALSE", - symbol="BTCUSDT", - type="BORROW" + asset="BTC", amount=0.1, isIsolated="FALSE", symbol="BTCUSDT", type="BORROW" ) - + + @pytest.mark.skip(reason="can't test margin endpoints") async def test_margin_get_borrow_repay_records(clientAsync): await clientAsync.margin_get_borrow_repay_records( asset="BTC", isolatedSymbol="BTCUSDT", ) - + + @pytest.mark.skip(reason="can't test margin endpoints") async def test_margin_interest_rate_history(clientAsync): await clientAsync.margin_interest_rate_history( asset="BTC", ) - + + @pytest.mark.skip(reason="can't test margin endpoints") async def test_margin_max_borrowable(clientAsync): await clientAsync.margin_max_borrowable( asset="BTC", ) - + + async def test_time_unit_microseconds(): micro_client = AsyncClient( api_key, api_secret, https_proxy=proxy, testnet=testnet, time_unit="MICROSECOND" @@ -227,6 +264,7 @@ async def test_time_unit_microseconds(): ) await micro_client.close_connection() + async def test_time_unit_milloseconds(): milli_client = AsyncClient( api_key, api_secret, https_proxy=proxy, testnet=testnet, time_unit="MILLISECOND" @@ -241,7 +279,8 @@ async def test_time_unit_milloseconds(): async def test_handle_response(clientAsync): # Create base response object mock_response = ClientResponse( - 'GET', URL('http://test.com'), + "GET", + URL("http://test.com"), request_info=None, writer=None, continue100=None, @@ -251,7 +290,7 @@ async def test_handle_response(clientAsync): session=None, ) # Initialize headers - mock_response._headers = {hdrs.CONTENT_TYPE: 'application/json'} + mock_response._headers = {hdrs.CONTENT_TYPE: "application/json"} # Test successful JSON response mock_response.status = 200 @@ -260,18 +299,17 @@ async def test_handle_response(clientAsync): # Test empty response mock_response.status = 200 - mock_response._body = b'' + mock_response._body = b"" assert await clientAsync._handle_response(mock_response) == {} # Test invalid JSON response mock_response.status = 200 - mock_response._body = b'invalid json' + mock_response._body = b"invalid json" with pytest.raises(BinanceRequestException): await clientAsync._handle_response(mock_response) # Test error status code mock_response.status = 400 - mock_response._body = b'error message' + mock_response._body = b"error message" with pytest.raises(BinanceAPIException): await clientAsync._handle_response(mock_response) - \ No newline at end of file diff --git a/tests/test_async_client_futures.py b/tests/test_async_client_futures.py index f6a82dd62..d625b9737 100644 --- a/tests/test_async_client_futures.py +++ b/tests/test_async_client_futures.py @@ -4,151 +4,191 @@ from .test_order import assert_contract_order from .test_get_order_book import assert_ob -pytestmark = [pytest.mark.futures, pytest.mark.asyncio] +pytestmark = [pytest.mark.futures, pytest.mark.asyncio, pytest.mark.live] + async def test_futures_ping(futuresClientAsync): await futuresClientAsync.futures_ping() + async def test_futures_time(futuresClientAsync): await futuresClientAsync.futures_time() + + async def test_futures_exchange_info(futuresClientAsync): await futuresClientAsync.futures_exchange_info() + async def test_futures_order_book(futuresClientAsync): order_book = await futuresClientAsync.futures_order_book(symbol="BTCUSDT") assert_ob(order_book) + async def test_futures_rpi_depth(futuresClientAsync): rpi_depth = await futuresClientAsync.futures_rpi_depth(symbol="BTCUSDT") assert_ob(rpi_depth) + async def test_futures_recent_trades(futuresClientAsync): await futuresClientAsync.futures_recent_trades(symbol="BTCUSDT") + async def test_futures_historical_trades(futuresClientAsync): await futuresClientAsync.futures_historical_trades(symbol="BTCUSDT") + async def test_futures_aggregate_trades(futuresClientAsync): await futuresClientAsync.futures_aggregate_trades(symbol="BTCUSDT") + async def test_futures_klines(futuresClientAsync): await futuresClientAsync.futures_klines(symbol="BTCUSDT", interval="1h") + async def test_futures_continuous_klines(futuresClientAsync): await futuresClientAsync.futures_continuous_klines( pair="BTCUSDT", contractType="PERPETUAL", interval="1h" ) + async def test_futures_historical_klines(futuresClientAsync): await futuresClientAsync.futures_historical_klines( symbol="BTCUSDT", interval="1h", start_str=datetime.now().strftime("%Y-%m-%d") ) + async def test_futures_historical_klines_generator(futuresClientAsync): await futuresClientAsync.futures_historical_klines_generator( symbol="BTCUSDT", interval="1h", start_str=datetime.now().strftime("%Y-%m-%d") ) + async def test_futures_mark_price(futuresClientAsync): await futuresClientAsync.futures_mark_price() + async def test_futures_funding_rate(futuresClientAsync): await futuresClientAsync.futures_funding_rate() + @pytest.mark.skip(reason="No Sandbox Environment to test") async def test_futures_top_longshort_account_ratio(futuresClientAsync): await futuresClientAsync.futures_top_longshort_account_ratio( symbol="BTCUSDT", period="5m" ) + @pytest.mark.skip(reason="No Sandbox Environment to test") async def test_futures_top_longshort_position_ratio(futuresClientAsync): await futuresClientAsync.futures_top_longshort_position_ratio( symbol="BTCUSDT", period="5m" ) + @pytest.mark.skip(reason="No Sandbox Environment to test") async def test_futures_global_longshort_ratio(futuresClientAsync): await futuresClientAsync.futures_global_longshort_ratio( symbol="BTCUSDT", period="5m" ) + @pytest.mark.skip(reason="No Sandbox Environment to test") async def test_futures_taker_longshort_ratio(futuresClientAsync): await futuresClientAsync.futures_taker_longshort_ratio( symbol="BTCUSDT", period="5m" ) + async def test_futures_ticker(futuresClientAsync): await futuresClientAsync.futures_ticker() + async def test_futures_symbol_ticker(futuresClientAsync): await futuresClientAsync.futures_symbol_ticker() + async def test_futures_orderbook_ticker(futuresClientAsync): await futuresClientAsync.futures_orderbook_ticker() + async def test_futures_index_index_price_constituents(futuresClientAsync): await futuresClientAsync.futures_index_price_constituents(symbol="BTCUSD") + async def test_futures_liquidation_orders(futuresClientAsync): await futuresClientAsync.futures_liquidation_orders() + @pytest.mark.skip(reason="Temporary skip due to issues with api") async def test_futures_api_trading_status(futuresClientAsync): await futuresClientAsync.futures_api_trading_status() + async def test_futures_commission_rate(futuresClientAsync): await futuresClientAsync.futures_commission_rate(symbol="BTCUSDT") + async def test_futures_adl_quantile_estimate(futuresClientAsync): await futuresClientAsync.futures_adl_quantile_estimate() + async def test_futures_open_interest(futuresClientAsync): await futuresClientAsync.futures_open_interest(symbol="BTCUSDT") + async def test_futures_index_info(futuresClientAsync): await futuresClientAsync.futures_index_info() + @pytest.mark.skip(reason="No Sandbox Environment to test") async def test_futures_open_interest_hist(futuresClientAsync): await futuresClientAsync.futures_open_interest_hist(symbol="BTCUSDT", period="5m") + async def test_futures_leverage_bracket(futuresClientAsync): await futuresClientAsync.futures_leverage_bracket() + @pytest.mark.skip(reason="Not implemented") async def test_futures_account_transfer(futuresClientAsync): await futuresClientAsync.futures_account_transfer() + @pytest.mark.skip(reason="Not implemented") async def test_transfer_history(client): client.transfer_history() + @pytest.mark.skip(reason="Not implemented") async def test_futures_loan_borrow_history(futuresClientAsync): await futuresClientAsync.futures_loan_borrow_history() + @pytest.mark.skip(reason="Not implemented") async def test_futures_loan_repay_history(futuresClientAsync): await futuresClientAsync.futures_loan_repay_history() + @pytest.mark.skip(reason="Not implemented") async def test_futures_loan_wallet(futuresClientAsync): await futuresClientAsync.futures_loan_wallet() + @pytest.mark.skip(reason="Not implemented") async def test_futures_cross_collateral_adjust_history(futuresClientAsync): await futuresClientAsync.futures_cross_collateral_adjust_history() + @pytest.mark.skip(reason="Not implemented") async def test_futures_cross_collateral_liquidation_history(futuresClientAsync): await futuresClientAsync.futures_cross_collateral_liquidation_history() + @pytest.mark.skip(reason="Not implemented") async def test_futures_loan_interest_history(futuresClientAsync): await futuresClientAsync.futures_loan_interest_history() + async def test_futures_create_get_edit_cancel_order(futuresClientAsync): ticker = await futuresClientAsync.futures_ticker(symbol="LTCUSDT") positions = await futuresClientAsync.futures_position_information(symbol="LTCUSDT") @@ -178,6 +218,7 @@ async def test_futures_create_get_edit_cancel_order(futuresClientAsync): orderid=order["orderId"], symbol=order["symbol"] ) + async def test_futures_create_test_order(futuresClientAsync): ticker = await futuresClientAsync.futures_ticker(symbol="LTCUSDT") positions = await futuresClientAsync.futures_position_information(symbol="LTCUSDT") @@ -191,6 +232,7 @@ async def test_futures_create_test_order(futuresClientAsync): price=str(round(float(ticker["lastPrice"]) - 1, 0)), ) + async def test_futures_place_batch_order_and_cancel(futuresClientAsync): ticker = await futuresClientAsync.futures_ticker(symbol="LTCUSDT") positions = await futuresClientAsync.futures_position_information(symbol="LTCUSDT") @@ -234,27 +276,34 @@ async def test_futures_place_batch_order_and_cancel(futuresClientAsync): for order in cancelled_orders: assert_contract_order(futuresClientAsync, order) + async def test_futures_get_open_orders(futuresClientAsync): await futuresClientAsync.futures_get_open_orders() + async def test_futures_get_all_orders(futuresClientAsync): orders = futuresClientAsync.futures_get_all_orders() print(orders) + async def test_futures_cancel_all_open_orders(futuresClientAsync): await futuresClientAsync.futures_cancel_all_open_orders(symbol="LTCUSDT") + async def test_futures_countdown_cancel_all(futuresClientAsync): await futuresClientAsync.futures_countdown_cancel_all( symbol="LTCUSDT", countdownTime=10 ) + async def test_futures_account_balance(futuresClientAsync): await futuresClientAsync.futures_account_balance() + async def test_futures_account(futuresClientAsync): await futuresClientAsync.futures_account() + async def test_futures_symbol_adl_risk(futuresClientAsync): # Test without symbol (get all) adl_risks = await futuresClientAsync.futures_symbol_adl_risk() @@ -270,9 +319,11 @@ async def test_futures_symbol_adl_risk(futuresClientAsync): assert adl_risk["adlRisk"] in ["low", "medium", "high"] assert adl_risk["symbol"] == test_symbol + async def test_futures_change_leverage(futuresClientAsync): await futuresClientAsync.futures_change_leverage(symbol="LTCUSDT", leverage=10) + async def test_futures_change_margin_type(futuresClientAsync): try: await futuresClientAsync.futures_change_margin_type( @@ -283,20 +334,25 @@ async def test_futures_change_margin_type(futuresClientAsync): symbol="XRPUSDT", marginType="ISOLATED" ) + async def test_futures_position_margin_history(futuresClientAsync): position = await futuresClientAsync.futures_position_margin_history( symbol="LTCUSDT" ) + async def test_futures_position_information(futuresClientAsync): await futuresClientAsync.futures_position_information() + async def test_futures_account_trades(futuresClientAsync): await futuresClientAsync.futures_account_trades() + async def test_futures_income_history(futuresClientAsync): await futuresClientAsync.futures_income_history() + async def close_all_futures_positions(futuresClientAsync): # Get all open positions positions = await futuresClientAsync.futures_position_information(symbol="LTCUSDT") @@ -318,6 +374,7 @@ async def close_all_futures_positions(futuresClientAsync): except Exception as e: print(f"Failed to close position for {symbol}: {e}") + @pytest.mark.skip(reason="Not implemented") async def test_futures_get_and_change_position_mode(futuresClientAsync): mode = await futuresClientAsync.futures_get_position_mode() @@ -325,102 +382,130 @@ async def test_futures_get_and_change_position_mode(futuresClientAsync): dualSidePosition=not mode["dualSidePosition"] ) + @pytest.mark.skip(reason="Not implemented") async def test_futures_change_multi_assets_mode(futuresClientAsync): await futuresClientAsync.futures_change_multi_assets_mode() + async def test_futures_get_multi_assets_mode(futuresClientAsync): await futuresClientAsync.futures_get_multi_assets_mode() + async def test_futures_stream_get_listen_key(futuresClientAsync): await futuresClientAsync.futures_stream_get_listen_key() + @pytest.mark.skip(reason="Not implemented") async def test_futures_stream_close(futuresClientAsync): await futuresClientAsync.futures_stream_close() + # new methods async def test_futures_account_config(futuresClientAsync): await futuresClientAsync.futures_account_config() + async def test_futures_symbol_config(futuresClientAsync): await futuresClientAsync.futures_symbol_config() + # COIN Futures API async def test_futures_coin_ping(futuresClientAsync): await futuresClientAsync.futures_coin_ping() + async def test_futures_coin_time(futuresClientAsync): await futuresClientAsync.futures_coin_time() + async def test_futures_coin_exchange_info(futuresClientAsync): await futuresClientAsync.futures_coin_exchange_info() + async def test_futures_coin_order_book(futuresClientAsync): order_book = await futuresClientAsync.futures_coin_order_book(symbol="BTCUSD_PERP") assert_ob(order_book) + async def test_futures_coin_recent_trades(futuresClientAsync): await futuresClientAsync.futures_coin_recent_trades(symbol="BTCUSD_PERP") + async def test_futures_coin_historical_trades(futuresClientAsync): await futuresClientAsync.futures_coin_historical_trades(symbol="BTCUSD_PERP") + @pytest.mark.skip(reason="Not implemented") async def test_futures_coin_aggregate_trades(futuresClientAsync): await futuresClientAsync.futures_coin_aggregate_trades(symbol="BTCUSD_PERP") + async def test_futures_coin_klines(futuresClientAsync): await futuresClientAsync.futures_coin_klines(symbol="BTCUSD_PERP", interval="1h") + async def test_futures_coin_continous_klines(futuresClientAsync): await futuresClientAsync.futures_coin_continous_klines( pair="BTCUSD", contractType="PERPETUAL", interval="1h" ) + async def test_futures_coin_index_price_klines(futuresClientAsync): await futuresClientAsync.futures_coin_index_price_klines( pair="BTCUSD", interval="1m" ) + async def test_futures_coin_mark_price_klines(futuresClientAsync): await futuresClientAsync.futures_coin_mark_price_klines( symbol="BTCUSD_PERP", interval="1m" ) + async def test_futures_coin_mark_price(futuresClientAsync): await futuresClientAsync.futures_coin_mark_price() + @pytest.mark.skip(reason="Giving unknwon error from binance") async def test_futures_coin_funding_rate(futuresClientAsync): await futuresClientAsync.futures_coin_funding_rate(symbol="BTCUSD_PERP") + async def test_futures_coin_ticker(futuresClientAsync): await futuresClientAsync.futures_coin_ticker() + async def test_futures_coin_symbol_ticker(futuresClientAsync): await futuresClientAsync.futures_coin_symbol_ticker() + async def test_futures_coin_orderbook_ticker(futuresClientAsync): await futuresClientAsync.futures_coin_orderbook_ticker() + async def test_futures_coin_index_index_price_constituents(futuresClientAsync): await futuresClientAsync.futures_coin_index_price_constituents(symbol="BTCUSD") + @pytest.mark.skip(reason="Not implemented") async def test_futures_coin_liquidation_orders(futuresClientAsync): await futuresClientAsync.futures_coin_liquidation_orders() + async def test_futures_coin_open_interest(futuresClientAsync): await futuresClientAsync.futures_coin_open_interest(symbol="BTCUSD_PERP") + @pytest.mark.skip(reason="Not implemented") async def test_futures_coin_open_interest_hist(futuresClientAsync): await futuresClientAsync.futures_coin_open_interest_hist(symbol="BTCUSD_PERP") + async def test_futures_coin_leverage_bracket(futuresClientAsync): await futuresClientAsync.futures_coin_leverage_bracket() + @pytest.mark.skip(reason="Not implemented") async def test_futures_coin_create_order(futuresClientAsync): positions = await futuresClientAsync.futures_coin_position_information() @@ -450,88 +535,111 @@ async def test_futures_coin_create_order(futuresClientAsync): orderid=order["orderId"], symbol=order["symbol"] ) + @pytest.mark.skip(reason="Not implemented") async def test_futures_coin_place_batch_order(futuresClientAsync): await futuresClientAsync.futures_coin_place_batch_order() + @pytest.mark.skip(reason="Not implemented") async def test_futures_coin_get_order(futuresClientAsync): await futuresClientAsync.futures_coin_get_order() + async def test_futures_coin_get_open_orders(futuresClientAsync): await futuresClientAsync.futures_coin_get_open_orders() + @pytest.mark.skip(reason="Not implemented") async def test_futures_coin_get_all_orders(futuresClientAsync): await futuresClientAsync.futures_coin_get_all_orders() + @pytest.mark.skip(reason="Not implemented") async def test_futures_coin_cancel_order(futuresClientAsync): await futuresClientAsync.futures_coin_cancel_order() + async def test_futures_coin_cancel_all_open_orders(futuresClientAsync): await futuresClientAsync.futures_coin_cancel_all_open_orders(symbol="BTCUSD_PERP") + @pytest.mark.skip(reason="Not implemented") async def test_futures_coin_cancel_orders(futuresClientAsync): await futuresClientAsync.futures_coin_cancel_orders() + async def test_futures_coin_account_balance(futuresClientAsync): await futuresClientAsync.futures_coin_account_balance() + async def test_futures_coin_account(futuresClientAsync): await futuresClientAsync.futures_coin_account() + @pytest.mark.skip(reason="Not implemented") async def test_futures_coin_change_leverage(futuresClientAsync): await futuresClientAsync.futures_coin_change_leverage(symbol="XRPUSDT", leverage=10) + @pytest.mark.skip(reason="Not implemented") async def test_futures_coin_change_margin_type(futuresClientAsync): await futuresClientAsync.futures_coin_change_margin_type() + @pytest.mark.skip(reason="Not implemented") async def test_futures_coin_change_position_margin(futuresClientAsync): await futuresClientAsync.futures_coin_change_position_margin() + @pytest.mark.skip(reason="Not implemented") async def test_futures_coin_position_margin_history(futuresClientAsync): await futuresClientAsync.futures_coin_position_margin_history() + async def test_futures_coin_position_information(futuresClientAsync): await futuresClientAsync.futures_coin_position_information() + @pytest.mark.skip(reason="Not implemented") async def test_futures_coin_account_trades(futuresClientAsync): await futuresClientAsync.futures_coin_account_trades() + @pytest.mark.skip(reason="Not implemented") async def test_futures_coin_income_history(futuresClientAsync): await futuresClientAsync.futures_coin_income_history() + @pytest.mark.skip(reason="Not implemented") async def test_futures_coin_change_position_mode(futuresClientAsync): await futuresClientAsync.futures_coin_change_position_mode() + async def test_futures_coin_get_position_mode(futuresClientAsync): await futuresClientAsync.futures_coin_get_position_mode() + async def test_futures_coin_stream_close(futuresClientAsync): listen_key = await futuresClientAsync.futures_coin_stream_get_listen_key() await futuresClientAsync.futures_coin_stream_close(listenKey=listen_key) + @pytest.mark.skip(reason="No sandbox support") async def test_futures_coin_account_order_history_download(futuresClientAsync): await futuresClientAsync.futures_coin_account_order_download() + @pytest.mark.skip(reason="No sandbox support") async def test_futures_coin_account_order_download_id(futuresClientAsync): await futuresClientAsync.futures_coin_account_order_download_link(downloadId="123") + @pytest.mark.skip(reason="No sandbox support") async def test_futures_coin_account_trade_history_download(futuresClientAsync): await futuresClientAsync.futures_coin_account_trade_history_download() + @pytest.mark.skip(reason="No sandbox support") async def test_futures_coin_account_trade_download_id(futuresClientAsync): await futuresClientAsync.futures_coin_account_trade_history_download_link( @@ -602,7 +710,9 @@ async def test_futures_get_algo_order_async(futuresClientAsync): ) assert fetched_order["algoId"] == algo_id # Clean up - await futuresClientAsync.futures_cancel_algo_order(symbol=ticker["symbol"], algoId=algo_id) + await futuresClientAsync.futures_cancel_algo_order( + symbol=ticker["symbol"], algoId=algo_id + ) async def test_futures_get_all_algo_orders_async(futuresClientAsync): @@ -639,7 +749,9 @@ async def test_futures_cancel_algo_order_async(futuresClientAsync): async def test_futures_cancel_all_algo_open_orders_async(futuresClientAsync): """Test canceling all open algo orders async""" - result = await futuresClientAsync.futures_cancel_all_algo_open_orders(symbol="LTCUSDT") + result = await futuresClientAsync.futures_cancel_all_algo_open_orders( + symbol="LTCUSDT" + ) assert "code" in result or "msg" in result @@ -666,13 +778,15 @@ async def test_futures_create_algo_order_with_price_protect_async(futuresClientA ) -@pytest.mark.skip(reason="TRAILING_STOP_MARKET with activatePrice may not be fully supported in testnet environment") +@pytest.mark.skip( + reason="TRAILING_STOP_MARKET with activatePrice may not be fully supported in testnet environment" +) async def test_futures_create_algo_order_trailing_stop_async(futuresClientAsync): """Test creating a TRAILING_STOP_MARKET algo order with activatePrice and callbackRate async""" ticker = await futuresClientAsync.futures_ticker(symbol="LTCUSDT") positions = await futuresClientAsync.futures_position_information(symbol="LTCUSDT") current_price = float(ticker["lastPrice"]) - + order = await futuresClientAsync.futures_create_algo_order( symbol=ticker["symbol"], side="SELL", @@ -740,7 +854,9 @@ async def test_futures_create_algo_order_with_price_match_async(futuresClientAsy ) -async def test_futures_create_algo_order_with_new_order_resp_type_async(futuresClientAsync): +async def test_futures_create_algo_order_with_new_order_resp_type_async( + futuresClientAsync, +): """Test creating an algo order with newOrderRespType parameter async""" ticker = await futuresClientAsync.futures_ticker(symbol="LTCUSDT") positions = await futuresClientAsync.futures_position_information(symbol="LTCUSDT") @@ -784,4 +900,3 @@ async def test_futures_create_algo_order_with_working_type_async(futuresClientAs await futuresClientAsync.futures_cancel_algo_order( symbol=ticker["symbol"], algoId=order["algoId"] ) - diff --git a/tests/test_async_client_gift_card copy.py b/tests/test_async_client_gift_card copy.py index 4b76b929f..2bc863760 100644 --- a/tests/test_async_client_gift_card copy.py +++ b/tests/test_async_client_gift_card copy.py @@ -1,6 +1,6 @@ import pytest -pytestmark = [pytest.mark.gift_card, pytest.mark.asyncio] +pytestmark = [pytest.mark.gift_card, pytest.mark.asyncio, pytest.mark.live] async def test_gift_card_fetch_token_limit(liveClientAsync): diff --git a/tests/test_async_client_margin.py b/tests/test_async_client_margin.py index a13f7c099..504abbedb 100644 --- a/tests/test_async_client_margin.py +++ b/tests/test_async_client_margin.py @@ -1,418 +1,561 @@ import pytest -pytestmark = [pytest.mark.margin, pytest.mark.asyncio] +pytestmark = [pytest.mark.margin, pytest.mark.asyncio, pytest.mark.live] + async def test_margin__get_account_status(asyncClient): await asyncClient.get_account_status() + async def test_margin_get_account_api_trading_status(asyncClient): await asyncClient.get_account_api_trading_status() + async def test_margin_get_account_api_permissions(asyncClient): await asyncClient.get_account_api_permissions() + async def test_margin_get_dust_assets(asyncClient): await asyncClient.get_dust_assets() + async def test_margin_get_dust_log(asyncClient): await asyncClient.test_get_dust_log() + async def test_margin_transfer_dust(asyncClient): await asyncClient.transfer_dust() + async def test_margin_get_asset_dividend_history(asyncClient): await asyncClient.get_asset_dividend_history() + async def test_margin_make_universal_transfer(asyncClient): await asyncClient.make_universal_transfer() + async def test_margin_query_universal_transfer_history(asyncClient): await asyncClient.query_universal_transfer_history() + async def test_margin_get_trade_fee(asyncClient): await asyncClient.get_trade_fee() + async def test_margin_get_asset_details(asyncClient): await asyncClient.get_asset_details() + async def test_margin_get_spot_delist_schedule(asyncClient): await asyncClient.get_spot_delist_schedule() + # Withdraw Endpoints + async def test_margin_withdraw(asyncClient): await asyncClient.withdraw() + async def test_margin_get_deposit_history(asyncClient): await asyncClient.get_deposit_history() + async def test_margin_get_withdraw_history(asyncClient): await asyncClient.get_withdraw_history() + async def test_margin_get_withdraw_history_id(asyncClient): await asyncClient.get_withdraw_history_id() + async def test_margin_get_deposit_address(asyncClient): await asyncClient.get_deposit_address() + # Margin Trading Endpoints + async def test_margin_get_margin_account(asyncClient): await asyncClient.get_margin_account() + async def test_margin_get_isolated_margin_account(asyncClient): await asyncClient.get_isolated_margin_account() + async def test_margin_enable_isolated_margin_account(asyncClient): await asyncClient.enable_isolated_margin_account() + async def test_margin_disable_isolated_margin_account(asyncClient): await asyncClient.disable_isolated_margin_account() + async def test_margin_get_enabled_isolated_margin_account_limit(asyncClient): await asyncClient.get_enabled_isolated_margin_account_limit() + async def test_margin_get_margin_dustlog(asyncClient): await asyncClient.get_margin_dustlog() + async def test_margin_get_margin_dust_assets(asyncClient): await asyncClient.get_margin_dust_assets() + async def test_margin_transfer_margin_dust(asyncClient): await asyncClient.transfer_margin_dust() + async def test_margin_get_cross_margin_collateral_ratio(asyncClient): await asyncClient.get_cross_margin_collateral_ratio() + async def test_margin_get_small_liability_exchange_assets(asyncClient): await asyncClient.get_small_liability_exchange_assets() + async def test_margin_exchange_small_liability_assets(asyncClient): await asyncClient.exchange_small_liability_assets() + async def test_margin_get_small_liability_exchange_history(asyncClient): await asyncClient.get_small_liability_exchange_history() + async def test_margin_get_future_hourly_interest_rate(asyncClient): await asyncClient.get_future_hourly_interest_rate() + async def test_margin_get_margin_capital_flow(asyncClient): await asyncClient.get_margin_capital_flow() + async def test_margin_get_margin_asset(asyncClient): await asyncClient.get_margin_asset() + async def test_margin_get_margin_symbol(asyncClient): await asyncClient.get_margin_symbol() + async def test_margin_get_margin_all_assets(asyncClient): await asyncClient.get_margin_all_assets() + async def test_margin_get_margin_all_pairs(asyncClient): await asyncClient.get_margin_all_pairs() + async def test_margin_create_isolated_margin_account(asyncClient): await asyncClient.create_isolated_margin_account() + async def test_margin_get_isolated_margin_symbol(asyncClient): await asyncClient.get_isolated_margin_symbol() + async def test_margin_get_all_isolated_margin_symbols(asyncClient): await asyncClient.get_all_isolated_margin_symbols() + async def test_margin_get_isolated_margin_fee_data(asyncClient): await asyncClient.get_isolated_margin_fee_data() + async def test_margin_get_isolated_margin_tier_data(asyncClient): await asyncClient.get_isolated_margin_tier_data() + async def test_margin_margin_manual_liquidation(asyncClient): await asyncClient.margin_manual_liquidation() + async def test_margin_toggle_bnb_burn_spot_margin(asyncClient): await asyncClient.toggle_bnb_burn_spot_margin() + async def test_margin_get_bnb_burn_spot_margin(asyncClient): await asyncClient.get_bnb_burn_spot_margin() + async def test_margin_get_margin_price_index(asyncClient): await asyncClient.get_margin_price_index() + async def test_margin_transfer_margin_to_spot(asyncClient): await asyncClient.transfer_margin_to_spot() + async def test_margin_transfer_spot_to_margin(asyncClient): await asyncClient.transfer_spot_to_margin() + async def test_margin_transfer_isolated_margin_to_spot(asyncClient): await asyncClient.transfer_isolated_margin_to_spot() + async def test_margin_transfer_spot_to_isolated_margin(asyncClient): await asyncClient.transfer_spot_to_isolated_margin() + async def test_margin_get_isolated_margin_tranfer_history(asyncClient): await asyncClient.get_isolated_margin_tranfer_history() + async def test_margin_create_margin_loan(asyncClient): await asyncClient.create_margin_loan() + async def test_margin_repay_margin_loan(asyncClient): await asyncClient.repay_margin_loan() + async def create_margin_ordertest_(asyncClient): await asyncClient.create_margin_order() + async def test_margin_cancel_margin_order(asyncClient): await asyncClient.cancel_margin_order() + async def test_margin_set_margin_max_leverage(asyncClient): await asyncClient.set_margin_max_leverage() + async def test_margin_get_margin_transfer_history(asyncClient): await asyncClient.get_margin_transfer_history() + async def test_margin_get_margin_loan_details(asyncClient): await asyncClient.get_margin_loan_details() + async def test_margin_get_margin_repay_details(asyncClient): await asyncClient.get_margin_repay_details() + async def test_margin_get_cross_margin_data(asyncClient): await asyncClient.get_cross_margin_data() + async def test_margin_get_margin_interest_history(asyncClient): await asyncClient.get_margin_interest_history() + async def test_margin_get_margin_force_liquidation_rec(asyncClient): await asyncClient.get_margin_force_liquidation_rec() + async def test_margin_get_margin_order(asyncClient): await asyncClient.get_margin_order() + async def test_margin_get_open_margin_orders(asyncClient): await asyncClient.get_open_margin_orders() + async def test_margin_get_all_margin_orders(asyncClient): await asyncClient.get_all_margin_orders() + async def test_margin_get_margin_trades(asyncClient): await asyncClient.get_margin_trades() + async def test_margin_get_max_margin_loan(asyncClient): await asyncClient.get_max_margin_loan() + async def test_margin_get_max_margin_transfer(asyncClient): await asyncClient.get_max_margin_transfer() + async def test_margin_get_margin_delist_schedule(asyncClient): await asyncClient.get_margin_delist_schedule() + # Margin OCO + async def test_margin_create_margin_oco_order(asyncClient): await asyncClient.create_margin_oco_order() + async def test_margin_cancel_margin_oco_order(asyncClient): await asyncClient.cancel_margin_oco_order() + async def test_margin_get_margin_oco_order(asyncClient): await asyncClient.get_margin_oco_order() + async def test_margin_get_open_margin_oco_orders(asyncClient): await asyncClient.get_open_margin_oco_orders() + # Cross-margin + async def test_margin_margin_stream_get_listen_key(asyncClient): await asyncClient.margin_stream_get_listen_key() + async def test_margin_margin_stream_close(asyncClient): await asyncClient.margin_stream_close() + # Isolated margin + async def test_margin_isolated_margin_stream_get_listen_key(asyncClient): await asyncClient.isolated_margin_stream_get_listen_key() + async def test_margin_isolated_margin_stream_close(asyncClient): await asyncClient.isolated_margin_stream_close() + # Simple Earn Endpoints + async def test_margin_get_simple_earn_flexible_product_list(asyncClient): await asyncClient.get_simple_earn_flexible_product_list() + async def test_margin_get_simple_earn_locked_product_list(asyncClient): await asyncClient.get_simple_earn_locked_product_list() + async def test_margin_subscribe_simple_earn_flexible_product(asyncClient): await asyncClient.subscribe_simple_earn_flexible_product() + async def test_margin_subscribe_simple_earn_locked_product(asyncClient): await asyncClient.subscribe_simple_earn_locked_product() + async def test_margin_redeem_simple_earn_flexible_product(asyncClient): await asyncClient.redeem_simple_earn_flexible_product() + async def test_margin_redeem_simple_earn_locked_product(asyncClient): await asyncClient.redeem_simple_earn_locked_product() + async def test_margin_get_simple_earn_flexible_product_position(asyncClient): await asyncClient.get_simple_earn_flexible_product_position() + async def test_margin_get_simple_earn_locked_product_position(asyncClient): await asyncClient.get_simple_earn_locked_product_position() + async def test_margin_get_simple_earn_account(asyncClient): await asyncClient.get_simple_earn_account() + # Lending Endpoints + async def test_margin_get_fixed_activity_project_list(asyncClient): await asyncClient.get_fixed_activity_project_list() + async def test_margin_change_fixed_activity_to_daily_position(asyncClient): await asyncClient.change_fixed_activity_to_daily_position() + # Staking Endpoints + async def test_margin_get_staking_product_list(asyncClient): await asyncClient.get_staking_product_list() + async def test_margin_purchase_staking_product(asyncClient): await asyncClient.purchase_staking_product() + async def test_margin_redeem_staking_product(asyncClient): await asyncClient.redeem_staking_product() + async def test_margin_get_staking_position(asyncClient): await asyncClient.get_staking_position() + async def test_margin_get_staking_purchase_history(asyncClient): await asyncClient.get_staking_purchase_history() + async def test_margin_set_auto_staking(asyncClient): await asyncClient.set_auto_staking() + async def test_margin_get_personal_left_quota(asyncClient): await asyncClient.get_personal_left_quota() + # US Staking Endpoints + async def test_margin_get_staking_asset_us(asyncClient): await asyncClient.get_staking_asset_us() + async def test_margin_stake_asset_us(asyncClient): await asyncClient.stake_asset_us() + async def test_margin_unstake_asset_us(asyncClient): await asyncClient.unstake_asset_us() + async def test_margin_get_staking_balance_us(asyncClient): await asyncClient.get_staking_balance_us() + async def test_margin_get_staking_history_us(asyncClient): await asyncClient.get_staking_history_us() + async def test_margin_get_staking_rewards_history_us(asyncClient): await asyncClient.get_staking_rewards_history_us() + # Sub Accounts + async def test_margin_get_sub_account_list(asyncClient): await asyncClient.get_sub_account_list() + async def test_margin_get_sub_account_transfer_history(asyncClient): await asyncClient.get_sub_account_transfer_history() + async def test_margin_get_sub_account_futures_transfer_history(asyncClient): await asyncClient.get_sub_account_futures_transfer_history() + async def test_margin_create_sub_account_futures_transfer(asyncClient): await asyncClient.create_sub_account_futures_transfer() + async def test_margin_get_sub_account_assets(asyncClient): await asyncClient.get_sub_account_assets() + async def test_margin_query_subaccount_spot_summary(asyncClient): await asyncClient.query_subaccount_spot_summary() + async def test_margin_get_subaccount_deposit_address(asyncClient): await asyncClient.get_subaccount_deposit_address() + async def test_margin_get_subaccount_deposit_history(asyncClient): await asyncClient.get_subaccount_deposit_history() + async def test_margin_get_subaccount_futures_margin_status(asyncClient): await asyncClient.get_subaccount_futures_margin_status() + async def test_margin_enable_subaccount_margin(asyncClient): await asyncClient.enable_subaccount_margin() + async def test_margin_get_subaccount_margin_details(asyncClient): await asyncClient.get_subaccount_margin_details() + async def test_margin_get_subaccount_margin_summary(asyncClient): await asyncClient.get_subaccount_margin_summary() + async def test_margin_enable_subaccount_futures(asyncClient): await asyncClient.enable_subaccount_futures() + async def test_margin_get_subaccount_futures_details(asyncClient): await asyncClient.get_subaccount_futures_details() + async def test_margin_get_subaccount_futures_summary(asyncClient): await asyncClient.get_subaccount_futures_summary() + async def test_margin_get_subaccount_futures_positionrisk(asyncClient): await asyncClient.get_subaccount_futures_positionrisk() + async def test_margin_make_subaccount_futures_transfer(asyncClient): await asyncClient.make_subaccount_futures_transfer() + async def test_margin_make_subaccount_margin_transfer(asyncClient): await asyncClient.make_subaccount_margin_transfer() + async def test_margin_make_subaccount_to_subaccount_transfer(asyncClient): await asyncClient.make_subaccount_to_subaccount_transfer() + async def test_margin_make_subaccount_to_master_transfer(asyncClient): await asyncClient.make_subaccount_to_master_transfer() + async def test_margin_get_subaccount_transfer_history(asyncClient): await asyncClient.get_subaccount_transfer_history() + async def test_margin_make_subaccount_universal_transfer(asyncClient): await asyncClient.make_subaccount_universal_transfer() + async def test_margin_get_universal_transfer_history(asyncClient): await asyncClient.get_universal_transfer_history() + # Fiat Endpoints + async def test_margin_get_fiat_deposit_withdraw_history(asyncClient): await asyncClient.get_fiat_deposit_withdraw_history() + async def test_margin_get_fiat_payments_history(asyncClient): await asyncClient.get_fiat_payments_history() + # C2C Endpoints + async def test_margin_get_c2c_trade_history(asyncClient): await asyncClient.get_c2c_trade_history() + # Pay Endpoints + async def test_margin_get_pay_trade_history(asyncClient): await asyncClient.get_pay_trade_history() + # Convert Endpoints + async def test_margin_get_convert_trade_history(asyncClient): await asyncClient.get_convert_trade_history() + async def test_margin_convert_request_quote(asyncClient): await asyncClient.convert_request_quote() + async def test_margin_convert_accept_quote(asyncClient): await asyncClient.convert_accept_quote() diff --git a/tests/test_async_client_options.py b/tests/test_async_client_options.py index 3a43177d1..52592e3e9 100644 --- a/tests/test_async_client_options.py +++ b/tests/test_async_client_options.py @@ -1,97 +1,130 @@ import pytest import sys -pytestmark = [pytest.mark.options, pytest.mark.asyncio, pytest.mark.skipif(sys.version_info < (3, 8), reason="websockets_proxy Python 3.8+")] +pytestmark = [ + pytest.mark.options, + pytest.mark.asyncio, + pytest.mark.skipif( + sys.version_info < (3, 8), reason="websockets_proxy Python 3.8+" + ), + pytest.mark.live, +] + @pytest.fixture def options_symbol(liveClient): prices = liveClient.options_price() return prices[0]["symbol"] + async def test_options_ping(liveClientAsync): await liveClientAsync.options_ping() + async def test_options_time(liveClientAsync): await liveClientAsync.options_time() + @pytest.mark.skip(reason="Not implemented") async def test_options_info(liveClientAsync): await liveClientAsync.options_info() + async def test_options_exchange_info(liveClientAsync): await liveClientAsync.options_exchange_info() + async def test_options_index_price(liveClientAsync): await liveClientAsync.options_index_price(underlying="BTCUSDT") + async def test_options_price(liveClientAsync): prices = await liveClientAsync.options_price() + async def test_options_mark_price(liveClientAsync): await liveClientAsync.options_mark_price() + async def test_options_order_book(liveClientAsync, options_symbol): await liveClientAsync.options_order_book(symbol=options_symbol) + async def test_options_klines(liveClientAsync, options_symbol): await liveClientAsync.options_klines(symbol=options_symbol, interval="1m") + async def test_options_recent_trades(liveClientAsync, options_symbol): await liveClientAsync.options_recent_trades(symbol=options_symbol) + async def test_options_historical_trades(liveClientAsync, options_symbol): await liveClientAsync.options_historical_trades(symbol=options_symbol) + # Account and trading interface endpoints + @pytest.mark.skip(reason="No sandbox to environmnet to test") async def test_options_account_info(liveClientAsync): await liveClientAsync.options_account_info() + @pytest.mark.skip(reason="No sandbox to environmnet to test") async def test_options_funds_transfer(liveClientAsync): await liveClientAsync.options_funds_transfer() + @pytest.mark.skip(reason="No sandbox to environmnet to test") async def test_options_positions(liveClientAsync): await liveClientAsync.options_positions() + @pytest.mark.skip(reason="No sandbox to environmnet to test") async def test_options_bill(liveClientAsync): await liveClientAsync.options_bill() + @pytest.mark.skip(reason="No sandbox to environmnet to test") async def test_options_place_order(liveClientAsync): await liveClientAsync.options_place_order() + @pytest.mark.skip(reason="No sandbox to environmnet to test") async def test_test_options_place_batch_order(liveClientAsync): await liveClientAsync.test_options_place_batch_order() + @pytest.mark.skip(reason="No sandbox to environmnet to test") async def test_options_cancel_order(liveClientAsync): await liveClientAsync.options_cancel_order() + @pytest.mark.skip(reason="No sandbox to environmnet to test") async def test_options_cancel_batch_order(liveClientAsync): await liveClientAsync.options_cancel_batch_order() + @pytest.mark.skip(reason="No sandbox to environmnet to test") async def test_options_cancel_all_orders(liveClientAsync): await liveClientAsync.options_cancel_all_orders() + @pytest.mark.skip(reason="No sandbox to environmnet to test") async def test_options_query_order(liveClientAsync): await liveClientAsync.options_query_order() + @pytest.mark.skip(reason="No sandbox to environmnet to test") async def test_options_query_pending_orders(liveClientAsync): await liveClientAsync.options_query_pending_orders() + @pytest.mark.skip(reason="No sandbox to environmnet to test") async def test_options_query_order_history(liveClientAsync): await liveClientAsync.options_query_order_history() + @pytest.mark.skip(reason="No sandbox to environmnet to test") async def test_options_user_trades(liveClientAsync): await liveClientAsync.options_user_trades() diff --git a/tests/test_async_client_portfolio.py b/tests/test_async_client_portfolio.py index 824db17a0..b689c1939 100644 --- a/tests/test_async_client_portfolio.py +++ b/tests/test_async_client_portfolio.py @@ -1,7 +1,7 @@ import pytest # Apply the 'portfolio' mark to all tests in this file -pytestmark = [pytest.mark.portfolio, pytest.mark.asyncio] +pytestmark = [pytest.mark.portfolio, pytest.mark.asyncio, pytest.mark.live] async def test_papi_get_balance(client): diff --git a/tests/test_async_client_ws_api.py b/tests/test_async_client_ws_api.py index d85389420..7caea1946 100644 --- a/tests/test_async_client_ws_api.py +++ b/tests/test_async_client_ws_api.py @@ -1,48 +1,70 @@ import pytest import sys -pytestmark = [pytest.mark.skipif(sys.version_info < (3, 8), reason="websockets_proxy Python 3.8+"), pytest.mark.asyncio()] + +pytestmark = [ + pytest.mark.skipif( + sys.version_info < (3, 8), reason="websockets_proxy Python 3.8+" + ), + pytest.mark.asyncio(), + pytest.mark.live, +] + async def test_ws_get_order_book(clientAsync): await clientAsync.ws_get_order_book(symbol="BTCUSDT") + async def test_ws_get_recent_trades(clientAsync): await clientAsync.ws_get_recent_trades(symbol="BTCUSDT") + async def test_ws_get_historical_trades(clientAsync): await clientAsync.ws_get_historical_trades(symbol="BTCUSDT") + async def test_ws_get_aggregate_trades(clientAsync): await clientAsync.ws_get_aggregate_trades(symbol="BTCUSDT") + async def test_ws_get_klines(clientAsync): await clientAsync.ws_get_klines(symbol="BTCUSDT", interval="1m") + async def test_ws_get_uiKlines(clientAsync): await clientAsync.ws_get_uiKlines(symbol="BTCUSDT", interval="1m") + async def test_ws_get_avg_price(clientAsync): await clientAsync.ws_get_avg_price(symbol="BTCUSDT") + async def test_ws_get_ticker(clientAsync): await clientAsync.ws_get_ticker(symbol="BTCUSDT") + async def test_ws_get_trading_day_ticker(clientAsync): await clientAsync.ws_get_trading_day_ticker(symbol="BTCUSDT") + async def test_ws_get_symbol_ticker_window(clientAsync): await clientAsync.ws_get_symbol_ticker_window(symbol="BTCUSDT") + async def test_ws_get_symbol_ticker(clientAsync): await clientAsync.ws_get_symbol_ticker(symbol="BTCUSDT") + async def test_ws_get_orderbook_ticker(clientAsync): await clientAsync.ws_get_orderbook_ticker(symbol="BTCUSDT") + async def test_ws_ping(clientAsync): await clientAsync.ws_ping() + async def test_ws_get_time(clientAsync): await clientAsync.ws_get_time() + async def test_ws_get_exchange_info(clientAsync): await clientAsync.ws_get_exchange_info(symbol="BTCUSDT") diff --git a/tests/test_async_client_ws_futures_requests.py b/tests/test_async_client_ws_futures_requests.py index 3f347f230..5192a0c8a 100644 --- a/tests/test_async_client_ws_futures_requests.py +++ b/tests/test_async_client_ws_futures_requests.py @@ -6,6 +6,8 @@ from .test_get_order_book import assert_ob from .test_order import assert_contract_order +pytestmark = pytest.mark.live + try: from unittest.mock import patch # Python 3.8+ except ImportError: @@ -59,9 +61,9 @@ async def test_ws_futures_get_order_book_ticker(futuresClientAsync): @pytest.mark.skipif(sys.version_info < (3, 8), reason="websockets_proxy Python 3.8+") @pytest.mark.asyncio() async def test_ws_futures_create_get_edit_cancel_order_with_orjson(futuresClientAsync): - if 'orjson' not in sys.modules: + if "orjson" not in sys.modules: raise ImportError("orjson is not available") - + ticker = await futuresClientAsync.ws_futures_get_order_book_ticker(symbol="LTCUSDT") positions = await futuresClientAsync.ws_futures_v2_account_position( symbol="LTCUSDT" @@ -92,11 +94,16 @@ async def test_ws_futures_create_get_edit_cancel_order_with_orjson(futuresClient orderid=order["orderId"], symbol=order["symbol"] ) + @pytest.mark.skipif(sys.version_info < (3, 8), reason="websockets_proxy Python 3.8+") @pytest.mark.asyncio() -async def test_ws_futures_create_get_edit_cancel_order_without_orjson(futuresClientAsync): - with patch.dict('sys.modules', {'orjson': None}): - ticker = await futuresClientAsync.ws_futures_get_order_book_ticker(symbol="LTCUSDT") +async def test_ws_futures_create_get_edit_cancel_order_without_orjson( + futuresClientAsync, +): + with patch.dict("sys.modules", {"orjson": None}): + ticker = await futuresClientAsync.ws_futures_get_order_book_ticker( + symbol="LTCUSDT" + ) positions = await futuresClientAsync.ws_futures_v2_account_position( symbol="LTCUSDT" ) @@ -170,7 +177,11 @@ async def test_ws_futures_fail_to_connect(futuresClientAsync): await futuresClientAsync.close_connection() # Mock the WebSocket API's connect method to raise an exception - with patch.object(futuresClientAsync.ws_future, 'connect', side_effect=ConnectionError("Simulated connection failure")): + with patch.object( + futuresClientAsync.ws_future, + "connect", + side_effect=ConnectionError("Simulated connection failure"), + ): with pytest.raises(BinanceWebsocketUnableToConnect): await futuresClientAsync.ws_futures_get_order_book(symbol="BTCUSDT") @@ -180,7 +191,9 @@ async def test_ws_futures_fail_to_connect(futuresClientAsync): async def test_ws_futures_create_cancel_algo_order(futuresClientAsync): """Test creating and canceling an algo order via websocket async""" ticker = await futuresClientAsync.ws_futures_get_order_book_ticker(symbol="LTCUSDT") - positions = await futuresClientAsync.ws_futures_v2_account_position(symbol="LTCUSDT") + positions = await futuresClientAsync.ws_futures_v2_account_position( + symbol="LTCUSDT" + ) # Create an algo order order = await futuresClientAsync.ws_futures_create_algo_order( @@ -210,7 +223,9 @@ async def test_ws_futures_create_cancel_algo_order(futuresClientAsync): async def test_ws_futures_create_conditional_order_auto_routing(futuresClientAsync): """Test that conditional order types are automatically routed to algo endpoint""" ticker = await futuresClientAsync.ws_futures_get_order_book_ticker(symbol="LTCUSDT") - positions = await futuresClientAsync.ws_futures_v2_account_position(symbol="LTCUSDT") + positions = await futuresClientAsync.ws_futures_v2_account_position( + symbol="LTCUSDT" + ) # Create a STOP_MARKET order using ws_futures_create_order # It should automatically route to the algo endpoint @@ -241,7 +256,9 @@ async def test_ws_futures_create_conditional_order_auto_routing(futuresClientAsy async def test_ws_futures_conditional_order_with_stop_price(futuresClientAsync): """Test that stopPrice is converted to triggerPrice for conditional orders""" ticker = await futuresClientAsync.ws_futures_get_order_book_ticker(symbol="LTCUSDT") - positions = await futuresClientAsync.ws_futures_v2_account_position(symbol="LTCUSDT") + positions = await futuresClientAsync.ws_futures_v2_account_position( + symbol="LTCUSDT" + ) # Create a TAKE_PROFIT_MARKET order with stopPrice (should be converted to triggerPrice) # Use a price above current market price for SELL TAKE_PROFIT diff --git a/tests/test_client.py b/tests/test_client.py index c4b973e51..1437263bb 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -2,7 +2,15 @@ import pytest from binance.client import Client from binance.exceptions import BinanceAPIException, BinanceRequestException -from .conftest import proxies, api_key, api_secret, testnet, call_method_and_assert_uri_contains +from .conftest import ( + proxies, + api_key, + api_secret, + testnet, + call_method_and_assert_uri_contains, +) + +pytestmark = pytest.mark.live def test_client_initialization(client): @@ -24,8 +32,7 @@ def test_get_symbol_info(client): def test_ping(client): - call_method_and_assert_uri_contains(client, 'ping', '/v3/') - + call_method_and_assert_uri_contains(client, "ping", "/v3/") def test_get_server_time(client): @@ -59,9 +66,11 @@ def test_get_aggregate_trades(client): def test_get_klines(client): client.get_klines(symbol="BTCUSDT", interval="1d") + def test_get_ui_klines(client): client.get_ui_klines(symbol="BTCUSDT", interval="1d") + def test_get_avg_price(client): client.get_avg_price(symbol="BTCUSDT") @@ -75,7 +84,9 @@ def test_get_symbol_ticker(client): def test_get_orderbook_ticker(client): - call_method_and_assert_uri_contains(client, 'get_orderbook_ticker', '/v3/', symbol="BTCUSDT") + call_method_and_assert_uri_contains( + client, "get_orderbook_ticker", "/v3/", symbol="BTCUSDT" + ) def test_get_account(client): @@ -237,33 +248,37 @@ def test_time_unit_milloseconds(): def test_handle_response(client): # Test successful JSON response - mock_response = type('Response', (), { - 'status_code': 200, - 'text': '{"key": "value"}', - 'json': lambda: {"key": "value"} - }) + mock_response = type( + "Response", + (), + { + "status_code": 200, + "text": '{"key": "value"}', + "json": lambda: {"key": "value"}, + }, + ) assert client._handle_response(mock_response) == {"key": "value"} # Test empty response - mock_empty_response = type('Response', (), { - 'status_code': 200, - 'text': '' - }) + mock_empty_response = type("Response", (), {"status_code": 200, "text": ""}) assert client._handle_response(mock_empty_response) == {} # Test invalid JSON response - mock_invalid_response = type('Response', (), { - 'status_code': 200, - 'text': 'invalid json', - 'json': lambda: exec('raise ValueError()') - }) + mock_invalid_response = type( + "Response", + (), + { + "status_code": 200, + "text": "invalid json", + "json": lambda: exec("raise ValueError()"), + }, + ) with pytest.raises(BinanceRequestException): client._handle_response(mock_invalid_response) # Test error status code - mock_error_response = type('Response', (), { - 'status_code': 400, - 'text': 'error message' - }) + mock_error_response = type( + "Response", (), {"status_code": 400, "text": "error message"} + ) with pytest.raises(BinanceAPIException): client._handle_response(mock_error_response) diff --git a/tests/test_client_futures.py b/tests/test_client_futures.py index cc7977af5..a269b5058 100644 --- a/tests/test_client_futures.py +++ b/tests/test_client_futures.py @@ -6,6 +6,8 @@ from .test_order import assert_contract_order from .test_get_order_book import assert_ob +pytestmark = pytest.mark.live + def test_futures_ping(futuresClient): futuresClient.futures_ping() @@ -927,13 +929,15 @@ def test_futures_create_algo_order_with_price_protect(futuresClient): ) -@pytest.mark.skip(reason="TRAILING_STOP_MARKET with activatePrice may not be fully supported in testnet environment") +@pytest.mark.skip( + reason="TRAILING_STOP_MARKET with activatePrice may not be fully supported in testnet environment" +) def test_futures_create_algo_order_trailing_stop(futuresClient): """Test creating a TRAILING_STOP_MARKET algo order with activatePrice and callbackRate""" ticker = futuresClient.futures_ticker(symbol="LTCUSDT") positions = futuresClient.futures_position_information(symbol="LTCUSDT") current_price = float(ticker["lastPrice"]) - + # For SELL trailing stop: activatePrice should be above current price # For BUY trailing stop: activatePrice should be below current price order = futuresClient.futures_create_algo_order( @@ -1048,4 +1052,3 @@ def test_futures_create_algo_order_with_working_type(futuresClient): futuresClient.futures_cancel_algo_order( symbol=ticker["symbol"], algoId=order["algoId"] ) - diff --git a/tests/test_client_gift_card.py b/tests/test_client_gift_card.py index 0525783b5..f319d2c52 100644 --- a/tests/test_client_gift_card.py +++ b/tests/test_client_gift_card.py @@ -23,14 +23,17 @@ def test_mock_gift_card_fetch_token_limit(liveClient): assert response == expected_response +@pytest.mark.live def test_gift_card_fetch_token_limit(liveClient): liveClient.gift_card_fetch_token_limit(baseToken="BUSD") +@pytest.mark.live def test_gift_card_fetch_rsa_public_key(liveClient): liveClient.gift_card_fetch_rsa_public_key() +@pytest.mark.live def test_gift_card_create_verify_and_redeem(liveClient): # create a gift card response = liveClient.gift_card_create(token="USDT", amount=1.0) @@ -46,6 +49,7 @@ def test_gift_card_create_verify_and_redeem(liveClient): assert response["data"]["referenceNo"] == redeem_response["data"]["referenceNo"] +@pytest.mark.live def test_gift_card_create_dual_token_and_redeem(liveClient): response = liveClient.gift_card_create_dual_token( baseToken="USDT", faceToken="BNB", baseTokenAmount=1.0 diff --git a/tests/test_client_margin.py b/tests/test_client_margin.py index 07e2d54d1..6f7561a5e 100644 --- a/tests/test_client_margin.py +++ b/tests/test_client_margin.py @@ -1,7 +1,7 @@ import pytest -pytestmark = pytest.mark.margin +pytestmark = [pytest.mark.margin, pytest.mark.live] def test_margin__get_account_status(client): @@ -596,10 +596,7 @@ def test_margin_enable_fast_withdraw_switch(client): @pytest.mark.skip(reason="can't test margin endpoints") def test_margin_next_hourly_interest_rate(client): - client.margin_next_hourly_interest_rate( - assets="BTC", - isIsolated="FALSE" - ) + client.margin_next_hourly_interest_rate(assets="BTC", isIsolated="FALSE") @pytest.mark.skip(reason="can't test margin endpoints") @@ -612,11 +609,7 @@ def test_margin_interest_history(client): @pytest.mark.skip(reason="can't test margin endpoints") def test_margin_borrow_repay(client): client.margin_borrow_repay( - asset="BTC", - amount=0.1, - isIsolated="FALSE", - symbol="BTCUSDT", - type="BORROW" + asset="BTC", amount=0.1, isIsolated="FALSE", symbol="BTCUSDT", type="BORROW" ) @@ -629,15 +622,17 @@ def test_margin_get_borrow_repay_records(client): startTime=1563438204000, endTime=1563438204000, current=1, - size=10 + size=10, ) + @pytest.mark.skip(reason="can't test margin endpoints") def test_margin_interest_rate_history(client): client.margin_interest_rate_history( asset="BTC", ) + @pytest.mark.skip(reason="can't test margin endpoints") def test_margin_max_borrowable(client): client.margin_max_borrowable( diff --git a/tests/test_client_options.py b/tests/test_client_options.py index 4016980ec..27326f370 100644 --- a/tests/test_client_options.py +++ b/tests/test_client_options.py @@ -2,7 +2,13 @@ import sys -pytestmark = [pytest.mark.options, pytest.mark.skipif(sys.version_info < (3, 8), reason="websockets_proxy Python 3.8+")] +pytestmark = [ + pytest.mark.options, + pytest.mark.skipif( + sys.version_info < (3, 8), reason="websockets_proxy Python 3.8+" + ), + pytest.mark.live, +] @pytest.fixture diff --git a/tests/test_client_portfolio.py b/tests/test_client_portfolio.py index d8f657e72..db67ef83c 100644 --- a/tests/test_client_portfolio.py +++ b/tests/test_client_portfolio.py @@ -1,7 +1,7 @@ import pytest # Apply the 'portfolio' mark to all tests in this file -pytestmark = pytest.mark.portfolio +pytestmark = [pytest.mark.portfolio, pytest.mark.live] def test_papi_get_balance(client): diff --git a/tests/test_client_ws_api.py b/tests/test_client_ws_api.py index b1faa7d47..ded5bbfb3 100644 --- a/tests/test_client_ws_api.py +++ b/tests/test_client_ws_api.py @@ -4,7 +4,13 @@ from .conftest import proxies, api_key, api_secret, testnet from .test_get_order_book import assert_ob -pytestmark = [pytest.mark.skipif(sys.version_info < (3, 8), reason="websockets_proxy Python 3.8+")] +pytestmark = [ + pytest.mark.skipif( + sys.version_info < (3, 8), reason="websockets_proxy Python 3.8+" + ), + pytest.mark.live, +] + def test_ws_get_order_book(client): orderbook = client.ws_get_order_book(symbol="BTCUSDT") diff --git a/tests/test_client_ws_futures_requests.py b/tests/test_client_ws_futures_requests.py index 5c16f9255..a7a7dacb9 100644 --- a/tests/test_client_ws_futures_requests.py +++ b/tests/test_client_ws_futures_requests.py @@ -4,6 +4,8 @@ from .test_get_order_book import assert_ob from .test_order import assert_contract_order +pytestmark = pytest.mark.live + @pytest.mark.skipif(sys.version_info < (3, 8), reason="websockets_proxy Python 3.8+") def test_ws_futures_get_order_book(futuresClient): diff --git a/tests/test_get_order_book.py b/tests/test_get_order_book.py index 5ce74de27..d808f1f05 100644 --- a/tests/test_get_order_book.py +++ b/tests/test_get_order_book.py @@ -2,6 +2,8 @@ import sys from binance.exceptions import BinanceAPIException +pytestmark = pytest.mark.live + def assert_ob(order_book): assert isinstance(order_book, dict) diff --git a/tests/test_ping.py b/tests/test_ping.py index e2b599dd0..6a650c6dc 100644 --- a/tests/test_ping.py +++ b/tests/test_ping.py @@ -1,6 +1,8 @@ import os import pytest +pytestmark = pytest.mark.live + proxies = {} proxy = os.getenv("PROXY") diff --git a/tests/test_socket_manager.py b/tests/test_socket_manager.py index e5adb7b34..ac92bc6df 100644 --- a/tests/test_socket_manager.py +++ b/tests/test_socket_manager.py @@ -2,6 +2,8 @@ import pytest from .conftest import proxy +pytestmark = pytest.mark.live + def assert_message(msg): assert msg["stream"] == "!ticker@arr" diff --git a/tests/test_streams.py b/tests/test_streams.py index a2ab66348..589887926 100644 --- a/tests/test_streams.py +++ b/tests/test_streams.py @@ -5,6 +5,8 @@ from binance.async_client import AsyncClient from .conftest import proxy, api_key, api_secret, testnet +pytestmark = pytest.mark.live + @pytest.mark.skipif(sys.version_info < (3, 8), reason="websockets_proxy Python 3.8+") @pytest.mark.asyncio @@ -18,6 +20,7 @@ async def test_socket_stopped_on_aexit(clientAsync): assert ts2 is not ts1, "socket should be removed from _conn on exit" await clientAsync.close_connection() + @pytest.mark.skipif(sys.version_info < (3, 8), reason="websockets_proxy Python 3.8+") @pytest.mark.asyncio async def test_socket_stopped_on_aexit_futures(futuresClientAsync): diff --git a/tests/test_streams_options.py b/tests/test_streams_options.py index 1fb91b7bf..8d3a31ea8 100644 --- a/tests/test_streams_options.py +++ b/tests/test_streams_options.py @@ -4,8 +4,11 @@ from binance import BinanceSocketManager pytestmark = [ - pytest.mark.skipif(sys.version_info < (3, 8), reason="websockets_proxy Python 3.8+"), - pytest.mark.asyncio + pytest.mark.skipif( + sys.version_info < (3, 8), reason="websockets_proxy Python 3.8+" + ), + pytest.mark.asyncio, + pytest.mark.live, ] # Configure logger for this module @@ -18,6 +21,7 @@ INTERVAL = "1m" DEPTH = "20" + async def test_options_ticker(clientAsync): """Test options ticker socket""" logger.info(f"Starting options ticker test for symbol: {OPTION_SYMBOL}") @@ -27,13 +31,16 @@ async def test_options_ticker(clientAsync): logger.debug("Waiting for ticker message...") msg = await ts.recv() logger.info(f"Received ticker message: {msg}") - assert msg['e'] == '24hrTicker' + assert msg["e"] == "24hrTicker" logger.info("Options ticker test completed successfully") await clientAsync.close_connection() + async def test_options_ticker_by_expiration(clientAsync): """Test options ticker by expiration socket""" - logger.info(f"Starting options ticker by expiration test for {UNDERLYING_SYMBOL}, expiration: {EXPIRATION_DATE}") + logger.info( + f"Starting options ticker by expiration test for {UNDERLYING_SYMBOL}, expiration: {EXPIRATION_DATE}" + ) bm = BinanceSocketManager(clientAsync) socket = bm.options_ticker_by_expiration_socket(UNDERLYING_SYMBOL, EXPIRATION_DATE) async with socket as ts: @@ -44,6 +51,7 @@ async def test_options_ticker_by_expiration(clientAsync): logger.info("Options ticker by expiration test completed successfully") await clientAsync.close_connection() + async def test_options_recent_trades(clientAsync): """Test options recent trades socket""" logger.info(f"Starting options recent trades test for {UNDERLYING_SYMBOL}") @@ -53,23 +61,27 @@ async def test_options_recent_trades(clientAsync): logger.debug("Waiting for trade message...") msg = await ts.recv() logger.info(f"Received trade message: {msg}") - assert msg['e'] == 'trade' + assert msg["e"] == "trade" logger.info("Options recent trades test completed successfully") await clientAsync.close_connection() + async def test_options_kline(clientAsync): """Test options kline socket""" - logger.info(f"Starting options kline test for {OPTION_SYMBOL}, interval: {INTERVAL}") + logger.info( + f"Starting options kline test for {OPTION_SYMBOL}, interval: {INTERVAL}" + ) bm = BinanceSocketManager(clientAsync) socket = bm.options_kline_socket(OPTION_SYMBOL, INTERVAL) async with socket as ts: logger.debug("Waiting for kline message...") msg = await ts.recv() logger.info(f"Received kline message: {msg}") - assert msg['e'] == 'kline' + assert msg["e"] == "kline" logger.info("Options kline test completed successfully") await clientAsync.close_connection() + async def test_options_depth(clientAsync): """Test options depth socket""" logger.info(f"Starting options depth test for {OPTION_SYMBOL}, depth: {DEPTH}") @@ -79,10 +91,11 @@ async def test_options_depth(clientAsync): logger.debug("Waiting for depth message...") msg = await ts.recv() logger.info(f"Received depth message: {msg}") - assert msg['e'] == 'depth' + assert msg["e"] == "depth" logger.info("Options depth test completed successfully") await clientAsync.close_connection() + async def test_options_multiplex(clientAsync): """Test options multiplex socket""" streams = [ @@ -96,13 +109,16 @@ async def test_options_multiplex(clientAsync): logger.debug("Waiting for multiplex message...") msg = await ts.recv() logger.info(f"Received multiplex message: {msg}") - assert 'stream' in msg + assert "stream" in msg logger.info("Options multiplex test completed successfully") await clientAsync.close_connection() + async def test_options_open_interest(clientAsync): """Test options open interest socket""" - logger.info(f"Starting options open interest test for {UNDERLYING_SYMBOL}, expiration: {EXPIRATION_DATE}") + logger.info( + f"Starting options open interest test for {UNDERLYING_SYMBOL}, expiration: {EXPIRATION_DATE}" + ) bm = BinanceSocketManager(clientAsync) socket = bm.options_open_interest_socket(UNDERLYING_SYMBOL, EXPIRATION_DATE) async with socket as ts: @@ -113,6 +129,7 @@ async def test_options_open_interest(clientAsync): logger.info("Options open interest test completed successfully") await clientAsync.close_connection() + async def test_options_mark_price(clientAsync): """Test options mark price socket""" logger.info(f"Starting options mark price test for {UNDERLYING_SYMBOL}") @@ -126,9 +143,10 @@ async def test_options_mark_price(clientAsync): logger.info("Options mark price test completed successfully") await clientAsync.close_connection() + async def test_options_index_price(clientAsync): """Test options index price socket""" - symbol = 'ETHUSDT' + symbol = "ETHUSDT" logger.info(f"Starting options index price test for {symbol}") bm = BinanceSocketManager(clientAsync) socket = bm.options_index_price_socket(symbol) @@ -136,6 +154,6 @@ async def test_options_index_price(clientAsync): logger.debug("Waiting for index price message...") msg = await ts.recv() logger.info(f"Received index price message: {msg}") - assert msg['e'] == 'index' + assert msg["e"] == "index" logger.info("Options index price test completed successfully") await clientAsync.close_connection() diff --git a/tests/test_threaded_socket_manager.py b/tests/test_threaded_socket_manager.py index d76f30cec..03e76df99 100644 --- a/tests/test_threaded_socket_manager.py +++ b/tests/test_threaded_socket_manager.py @@ -1,16 +1,18 @@ from binance import ThreadedWebsocketManager -from binance.client import Client import asyncio import time -from .conftest import proxies, api_key, api_secret, proxy +from .conftest import api_key, api_secret, proxy import pytest import sys import logging -pytestmark = pytest.mark.skipif( - sys.version_info <= (3, 8), - reason="These tests require Python 3.8+ for proper websocket proxy support" -) +pytestmark = [ + pytest.mark.skipif( + sys.version_info <= (3, 8), + reason="These tests require Python 3.8+ for proper websocket proxy support", + ), + pytest.mark.live, +] received_ohlcv = False received_depth = False @@ -21,11 +23,14 @@ logger = logging.getLogger(__name__) logger.setLevel(logging.DEBUG) -# Get real symbols from Binance API -client = Client(api_key, api_secret, {"proxies": proxies}) -exchange_info = client.get_exchange_info() -symbols = [info['symbol'].lower() for info in exchange_info['symbols']] -streams = [f"{symbol}@bookTicker" for symbol in symbols][0:100] # Take first 800 symbols + +@pytest.fixture(scope="module") +def exchange_symbols(client): + """Get real symbols from Binance API""" + exchange_info = client.get_exchange_info() + symbols = [info["symbol"].lower() for info in exchange_info["symbols"]] + return [f"{symbol}@bookTicker" for symbol in symbols][0:100] + def test_threaded_socket_manager(): logger.debug("Starting test_threaded_socket_manager") @@ -61,30 +66,32 @@ def handle_socket_message(msg): time.sleep(2) -def test_many_symbols_small_queue(): +def test_many_symbols_small_queue(exchange_symbols): logger.debug("Starting test_many_symbols_small_queue with queue size 1") - twm = ThreadedWebsocketManager(api_key, api_secret, https_proxy=proxy, testnet=True, max_queue_size=1) - + twm = ThreadedWebsocketManager( + api_key, api_secret, https_proxy=proxy, testnet=True, max_queue_size=1 + ) + error_received = False msg_received = False - + def handle_message(msg): nonlocal error_received, msg_received if msg.get("e") == "error": error_received = True - logger.debug("Received WebSocket error: %s", msg.get('m', 'Unknown error')) + logger.debug("Received WebSocket error: %s", msg.get("m", "Unknown error")) return msg_received = True logger.debug("Received valid message") - + try: logger.debug("Starting ThreadedWebsocketManager") twm.start() - logger.debug("Starting multiplex socket with %d streams", len(streams)) - twm.start_multiplex_socket(callback=handle_message, streams=streams) + logger.debug("Starting multiplex socket with %d streams", len(exchange_symbols)) + twm.start_multiplex_socket(callback=handle_message, streams=exchange_symbols) logger.debug("Waiting 10 seconds for messages") time.sleep(10) - + assert msg_received, "Should have received messages" finally: logger.debug("Cleaning up test_many_symbols_small_queue") @@ -92,33 +99,43 @@ def handle_message(msg): time.sleep(2) -def test_many_symbols_adequate_queue(): +def test_many_symbols_adequate_queue(exchange_symbols): logger.debug("Starting test_many_symbols_adequate_queue with queue size 200") - twm = ThreadedWebsocketManager(api_key, api_secret, https_proxy=proxy, testnet=True, max_queue_size=200) - + twm = ThreadedWebsocketManager( + api_key, api_secret, https_proxy=proxy, testnet=True, max_queue_size=200 + ) + messages_received = 0 error_received = False - + def handle_message(msg): nonlocal messages_received, error_received if msg.get("e") == "error": error_received = True - logger.debug("Received WebSocket error: %s", msg.get('m', 'Unknown error')) + logger.debug("Received WebSocket error: %s", msg.get("m", "Unknown error")) return - + messages_received += 1 if messages_received % 10 == 0: # Log every 10th message logger.debug("Processed %d messages", messages_received) - + try: logger.debug("Starting ThreadedWebsocketManager") twm.start() - logger.debug("Starting futures multiplex socket with %d streams", len(streams)) - twm.start_futures_multiplex_socket(callback=handle_message, streams=streams) + logger.debug( + "Starting futures multiplex socket with %d streams", len(exchange_symbols) + ) + twm.start_futures_multiplex_socket( + callback=handle_message, streams=exchange_symbols + ) logger.debug("Waiting 10 seconds for messages") time.sleep(10) - - logger.debug("Test completed. Messages received: %d, Errors: %s", messages_received, error_received) + + logger.debug( + "Test completed. Messages received: %d, Errors: %s", + messages_received, + error_received, + ) assert messages_received > 0, "Should have received some messages" assert not error_received, "Should not have received any errors" finally: @@ -127,34 +144,44 @@ def handle_message(msg): time.sleep(2) -def test_slow_async_callback_no_error(): +def test_slow_async_callback_no_error(exchange_symbols): logger.debug("Starting test_slow_async_callback_no_error with queue size 400") - twm = ThreadedWebsocketManager(api_key, api_secret, https_proxy=proxy, testnet=True, max_queue_size=400) - + twm = ThreadedWebsocketManager( + api_key, api_secret, https_proxy=proxy, testnet=True, max_queue_size=400 + ) + messages_processed = 0 error_received = False - + async def slow_async_callback(msg): nonlocal messages_processed, error_received if msg.get("e") == "error": error_received = True - logger.debug("Received WebSocket error: %s", msg.get('m', 'Unknown error')) + logger.debug("Received WebSocket error: %s", msg.get("m", "Unknown error")) return - + logger.debug("Processing message with 2 second delay") await asyncio.sleep(2) messages_processed += 1 logger.debug("Message processed. Total processed: %d", messages_processed) - + try: logger.debug("Starting ThreadedWebsocketManager") twm.start() - logger.debug("Starting futures multiplex socket with %d streams", len(streams)) - twm.start_futures_multiplex_socket(callback=slow_async_callback, streams=streams) + logger.debug( + "Starting futures multiplex socket with %d streams", len(exchange_symbols) + ) + twm.start_futures_multiplex_socket( + callback=slow_async_callback, streams=exchange_symbols + ) logger.debug("Waiting 10 seconds for messages") time.sleep(10) - - logger.debug("Test completed. Messages processed: %d, Errors: %s", messages_processed, error_received) + + logger.debug( + "Test completed. Messages processed: %d, Errors: %s", + messages_processed, + error_received, + ) assert messages_processed > 0, "Should have processed some messages" assert not error_received, "Should not have received any errors" finally: @@ -168,23 +195,22 @@ def test_no_internet_connection(): logger.debug("Starting test_no_internet_connection") invalid_proxy = "http://invalid.proxy:1234" logger.debug("Using invalid proxy: %s", invalid_proxy) - - with pytest.raises(RuntimeError, match="Binance Socket Manager failed to initialize after 5 seconds"): + + with pytest.raises( + RuntimeError, + match="Binance Socket Manager failed to initialize after 5 seconds", + ): twm = ThreadedWebsocketManager( - api_key, - api_secret, - https_proxy=invalid_proxy, - testnet=True + api_key, api_secret, https_proxy=invalid_proxy, testnet=True ) - + try: - logger.debug("Attempting to start ThreadedWebsocketManager with invalid proxy") + logger.debug( + "Attempting to start ThreadedWebsocketManager with invalid proxy" + ) twm.start() logger.debug("Attempting to start kline socket (should fail)") - twm.start_kline_socket( - callback=lambda x: print(x), - symbol="BTCUSDT" - ) + twm.start_kline_socket(callback=lambda x: print(x), symbol="BTCUSDT") finally: logger.debug("Cleaning up test_no_internet_connection") twm.stop() diff --git a/tests/test_user_socket_integration.py b/tests/test_user_socket_integration.py index b66a91890..236021b10 100644 --- a/tests/test_user_socket_integration.py +++ b/tests/test_user_socket_integration.py @@ -13,12 +13,15 @@ Run with: pytest tests/test_user_socket_integration.py -v """ + import asyncio import pytest import pytest_asyncio from binance import BinanceSocketManager +pytestmark = pytest.mark.live + @pytest_asyncio.fixture async def socket_manager(clientAsync): @@ -36,22 +39,27 @@ async def test_user_socket_has_separate_queue(self, clientAsync, socket_manager) async with user_socket: # Queues should be different objects - assert user_socket._queue is not clientAsync.ws_api._queue, \ + assert user_socket._queue is not clientAsync.ws_api._queue, ( "user_socket should have its own queue, not share ws_api's queue" + ) @pytest.mark.asyncio - async def test_user_socket_uses_ws_api_subscription(self, clientAsync, socket_manager): + async def test_user_socket_uses_ws_api_subscription( + self, clientAsync, socket_manager + ): """User socket should use ws_api subscription mechanism.""" user_socket = socket_manager.user_socket() async with user_socket: # Should be marked as using ws_api subscription - assert user_socket._uses_ws_api_subscription is True, \ + assert user_socket._uses_ws_api_subscription is True, ( "user_socket should be marked as using ws_api subscription" + ) # Should have a subscription ID - assert user_socket._subscription_id is not None, \ + assert user_socket._subscription_id is not None, ( "user_socket should have a subscription ID" + ) @pytest.mark.asyncio async def test_user_socket_no_read_loop(self, clientAsync, socket_manager): @@ -60,15 +68,19 @@ async def test_user_socket_no_read_loop(self, clientAsync, socket_manager): async with user_socket: # user_socket should not have started its own read loop - assert user_socket._handle_read_loop is None, \ + assert user_socket._handle_read_loop is None, ( "user_socket should not have its own read loop" + ) # ws_api should have a read loop - assert clientAsync.ws_api._handle_read_loop is not None, \ + assert clientAsync.ws_api._handle_read_loop is not None, ( "ws_api should have a read loop" + ) @pytest.mark.asyncio - async def test_user_socket_queue_registered_with_ws_api(self, clientAsync, socket_manager): + async def test_user_socket_queue_registered_with_ws_api( + self, clientAsync, socket_manager + ): """User socket's queue should be registered with ws_api for event routing.""" user_socket = socket_manager.user_socket() @@ -76,13 +88,15 @@ async def test_user_socket_queue_registered_with_ws_api(self, clientAsync, socke sub_id = user_socket._subscription_id # Subscription should be registered in ws_api - assert sub_id in clientAsync.ws_api._subscription_queues, \ + assert sub_id in clientAsync.ws_api._subscription_queues, ( "Subscription should be registered with ws_api" + ) # Registered queue should be user_socket's queue registered_queue = clientAsync.ws_api._subscription_queues[sub_id] - assert registered_queue is user_socket._queue, \ + assert registered_queue is user_socket._queue, ( "Registered queue should be user_socket's queue" + ) @pytest.mark.asyncio async def test_user_socket_cleanup_on_exit(self, clientAsync, socket_manager): @@ -95,8 +109,9 @@ async def test_user_socket_cleanup_on_exit(self, clientAsync, socket_manager): assert sub_id in clientAsync.ws_api._subscription_queues # After exit, subscription should be unregistered - assert sub_id not in clientAsync.ws_api._subscription_queues, \ + assert sub_id not in clientAsync.ws_api._subscription_queues, ( "Subscription should be unregistered after exit" + ) class TestUserSocketFunctionality: @@ -133,15 +148,18 @@ class TestNonUserSockets: """Tests verifying other socket types still work normally.""" @pytest.mark.asyncio - async def test_margin_socket_not_using_ws_api_subscription(self, clientAsync, socket_manager): + async def test_margin_socket_not_using_ws_api_subscription( + self, clientAsync, socket_manager + ): """Non-user KeepAliveWebsockets (like margin socket) should not use ws_api subscription.""" # margin_socket is a KeepAliveWebsocket with keepalive_type="margin" # Create it but don't connect - just check the flag margin_socket = socket_manager.margin_socket() # Before connecting, the flag should be False (default) - assert margin_socket._uses_ws_api_subscription is False, \ + assert margin_socket._uses_ws_api_subscription is False, ( "Margin socket should not use ws_api subscription" + ) # The _keepalive_type should be "margin", not "user" assert margin_socket._keepalive_type == "margin" @@ -156,10 +174,12 @@ async def test_ws_api_has_subscription_queues(self, clientAsync): # Ensure ws_api is initialized await clientAsync.ws_api._ensure_ws_connection() - assert hasattr(clientAsync.ws_api, '_subscription_queues'), \ + assert hasattr(clientAsync.ws_api, "_subscription_queues"), ( "ws_api should have _subscription_queues attribute" - assert isinstance(clientAsync.ws_api._subscription_queues, dict), \ + ) + assert isinstance(clientAsync.ws_api._subscription_queues, dict), ( "_subscription_queues should be a dict" + ) @pytest.mark.asyncio async def test_ws_api_register_unregister_queue(self, clientAsync): diff --git a/tests/test_ws_api.py b/tests/test_ws_api.py index 9b63db467..130eb8564 100644 --- a/tests/test_ws_api.py +++ b/tests/test_ws_api.py @@ -9,6 +9,8 @@ from .test_get_order_book import assert_ob from .conftest import proxy +pytestmark = pytest.mark.live + @pytest.mark.skipif(sys.version_info < (3, 8), reason="websockets_proxy Python 3.8+") @pytest.mark.asyncio @@ -59,7 +61,7 @@ async def test_invalid_request(clientAsync): # "APIError(code=-1100): Illegal characters found in parameter 'symbol'; legal range is \'^[\\\\w\\\\-._&&[^a-z]]{1,50}$\'." # ), # ): - + # # {'id': 'a2790cf96b11a8add71ebf', 'status': 400, 'error': {'code': -1100...:-1100,"msg":"Illegal characters found in parameter \'symbol\'; legal range is \'^[\\\\w\\\\-._&&[^a-z]]{1,50}$\'."}' # await clientAsync.ws_get_order_book(symbol="send error") @@ -133,25 +135,35 @@ async def test_message_handling(clientAsync): finally: await clientAsync.close_connection() + @pytest.mark.asyncio async def test_message_handling_raise_exception(clientAsync): try: with pytest.raises(BinanceAPIException): future = asyncio.Future() clientAsync.ws_api._responses["123"] = future - valid_msg = {"id": "123", "status": 400, "error": {"code": "0", "msg": "error message"}} + valid_msg = { + "id": "123", + "status": 400, + "error": {"code": "0", "msg": "error message"}, + } clientAsync.ws_api._handle_message(json.dumps(valid_msg)) await future finally: await clientAsync.close_connection() + @pytest.mark.asyncio async def test_message_handling_raise_exception_without_id(clientAsync): try: with pytest.raises(BinanceAPIException): future = asyncio.Future() clientAsync.ws_api._responses["123"] = future - valid_msg = {"id": "123", "status": 400, "error": {"code": "0", "msg": "error message"}} + valid_msg = { + "id": "123", + "status": 400, + "error": {"code": "0", "msg": "error message"}, + } clientAsync.ws_api._handle_message(json.dumps(valid_msg)) await future finally: @@ -214,7 +226,9 @@ async def test_ws_queue_overflow(clientAsync): # Check that we got valid responses or expected overflow errors valid_responses = [r for r in results if not isinstance(r, Exception)] - assert len(valid_responses) == len(symbols), "Should get at least one valid response" + assert len(valid_responses) == len(symbols), ( + "Should get at least one valid response" + ) for result in valid_responses: assert_ob(result) diff --git a/tox.ini b/tox.ini index 92335cb89..9f8befc4b 100644 --- a/tox.ini +++ b/tox.ini @@ -14,5 +14,24 @@ passenv = TEST_FUTURES_API_SECRET commands = pytest -n 5 -v tests/ --timeout=90 --doctest-modules --cov binance --cov-report term-missing --cov-report xml --reruns 3 --reruns-delay 30 +[testenv:offline] +deps = + -rtest-requirements.txt + -rrequirements.txt +commands = pytest -v tests/ -m "not live" --timeout=30 --strict-markers --cov binance --cov-report term-missing --cov-report xml + +[testenv:live] +deps = + -rtest-requirements.txt + -rrequirements.txt +passenv = + PROXY + TEST_TESTNET + TEST_API_KEY + TEST_API_SECRET + TEST_FUTURES_API_KEY + TEST_FUTURES_API_SECRET +commands = pytest -n 5 -v tests/ -m "live" --timeout=90 --cov binance --cov-report term-missing --cov-report xml --reruns 3 --reruns-delay 30 + [pep8] ignore = E501