Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions .github/workflows/python-package.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,11 @@ jobs:
- name: Run flake8 to check specific rules not covered by black
run: |
flake8 . --statistics
# Tests hit live production endpoints and depend on prod data
# availability, so they are not run in CI. Run them locally with
# Offline mocked tests run keyless (no DATAMAXI_API_KEY, no network).
# `-k "not integration"` excludes the live test_integration.py; the
# keyed test_call.py tests skip cleanly when no key is set.
- name: Run offline mocked tests
run: python -m pytest tests/ -k "not integration" -q
# The live test_integration.py hits prod endpoints and depends on prod
# data availability, so it is not run in CI. Run it locally with
# `DATAMAXI_API_KEY=... python -m pytest tests/`.
35 changes: 35 additions & 0 deletions tests/test_cex.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
"""Local (mocked) tests for the Cex aggregator wiring. No API key / network."""

import responses

from datamaxi.datamaxi.cex import Cex
from datamaxi.datamaxi.cex_candle import CexCandle
from datamaxi.datamaxi.cex_ticker import CexTicker
from datamaxi.datamaxi.cex_fee import CexFee
from datamaxi.datamaxi.cex_wallet_status import CexWalletStatus
from datamaxi.datamaxi.cex_announcement import CexAnnouncement
from datamaxi.datamaxi.cex_token import CexToken
from datamaxi.datamaxi.cex_symbol import CexSymbol
from tests.util import mock_http_response

BASE_URL = "https://api.datamaxiplus.com"


def _client():
return Cex(api_key="key", base_url=BASE_URL)


def test_cex_wires_subclients():
cex = _client()
assert isinstance(cex.candle, CexCandle)
assert isinstance(cex.ticker, CexTicker)
assert isinstance(cex.fee, CexFee)
assert isinstance(cex.wallet_status, CexWalletStatus)
assert isinstance(cex.announcement, CexAnnouncement)
assert isinstance(cex.token, CexToken)
assert isinstance(cex.symbol, CexSymbol)


@mock_http_response(responses.GET, "/api/v1/cex/candle/intervals", ["1m", "1h", "1d"])
def test_cex_candle_call_through_facade():
assert _client().candle.intervals() == ["1m", "1h", "1d"]
74 changes: 74 additions & 0 deletions tests/test_cex_announcement.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
"""Local (mocked) tests for the CexAnnouncement client. No API key / network."""

import re
import responses
import pytest
from urllib.parse import urlparse, parse_qs

from datamaxi.datamaxi.cex_announcement import CexAnnouncement
from datamaxi.error import ClientError, ServerError
from tests.util import mock_http_response

BASE_URL = "https://api.datamaxiplus.com"

_ANNOUNCE = {"data": [{"title": "New listing", "exchange": "binance"}]}


def _client():
return CexAnnouncement(api_key="key", base_url=BASE_URL)


def _qs(call):
return parse_qs(urlparse(call.request.url).query)


@mock_http_response(responses.GET, "/api/v1/cex/announcements", _ANNOUNCE)
def test_announcement_returns_response_and_next():
res, next_request = _client()()
assert res == _ANNOUNCE
assert callable(next_request)


@responses.activate
def test_announcement_sends_query_params():
responses.add(
responses.GET,
re.compile(".*/api/v1/cex/announcements.*"),
json=_ANNOUNCE,
status=200,
)
_client()(page=2, limit=50, exchange="binance")
qs = _qs(responses.calls[0])
assert qs["page"] == ["2"]
assert qs["limit"] == ["50"]
assert qs["sort"] == ["desc"]
assert qs["exchange"] == ["binance"]


def test_announcement_invalid_sort_raises_value_error():
with pytest.raises(ValueError):
_client()(sort="bogus")


def test_announcement_invalid_page_raises_value_error():
with pytest.raises(ValueError):
_client()(page=0)


@mock_http_response(
responses.GET,
"/api/v1/cex/announcements",
{"error": "bad request"},
http_status=400,
)
def test_announcement_client_error():
with pytest.raises(ClientError):
_client()()


@mock_http_response(
responses.GET, "/api/v1/cex/announcements", {"error": "boom"}, http_status=500
)
def test_announcement_server_error():
with pytest.raises(ServerError):
_client()()
88 changes: 88 additions & 0 deletions tests/test_cex_candle.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
"""Local (mocked) tests for the CexCandle client. No API key / network."""

import re
import responses
import pandas as pd
import pytest
from urllib.parse import urlparse, parse_qs

from datamaxi.datamaxi.cex_candle import CexCandle
from datamaxi.error import ClientError, ServerError
from tests.util import mock_http_response

BASE_URL = "https://api.datamaxiplus.com"

_CANDLE = {"data": [{"d": "1700000000", "o": "100", "h": "110", "l": "90", "c": "105"}]}


def _client():
return CexCandle(api_key="key", base_url=BASE_URL)


def _qs(call):
return parse_qs(urlparse(call.request.url).query)


@mock_http_response(responses.GET, "/api/v1/cex/candle", _CANDLE)
def test_candle_returns_dataframe():
df = _client()(exchange="binance", market="spot", symbol="BTC-USDT")
assert isinstance(df, pd.DataFrame)
assert "c" in df.columns
assert len(df) == 1


@responses.activate
def test_candle_sends_query_params():
responses.add(
responses.GET,
re.compile(".*/api/v1/cex/candle.*"),
json=_CANDLE,
status=200,
)
_client()(exchange="binance", market="spot", symbol="BTC-USDT", interval="1h")
qs = _qs(responses.calls[0])
assert qs["exchange"] == ["binance"]
assert qs["market"] == ["spot"]
assert qs["symbol"] == ["BTC-USDT"]
assert qs["interval"] == ["1h"]
assert qs["currency"] == ["USD"]
assert "from" not in qs and "to" not in qs


def test_candle_invalid_market_raises_value_error():
with pytest.raises(ValueError):
_client()(exchange="binance", market="bogus", symbol="BTC-USDT")


@mock_http_response(responses.GET, "/api/v1/cex/candle/exchanges", ["binance", "okx"])
def test_candle_exchanges_returns_list():
assert _client().exchanges(market="spot") == ["binance", "okx"]


@mock_http_response(
responses.GET, "/api/v1/cex/candle/symbols", [{"symbol": "BTC-USDT"}]
)
def test_candle_symbols_returns_list():
res = _client().symbols(exchange="binance", market="spot")
assert res == [{"symbol": "BTC-USDT"}]


@mock_http_response(responses.GET, "/api/v1/cex/candle/intervals", ["1m", "1h", "1d"])
def test_candle_intervals_returns_list():
assert _client().intervals() == ["1m", "1h", "1d"]


@mock_http_response(
responses.GET, "/api/v1/cex/candle", {"error": "bad request"}, http_status=400
)
def test_candle_client_error():
with pytest.raises(ClientError):
_client()(exchange="binance", market="spot", symbol="BTC-USDT")


@mock_http_response(
responses.GET, "/api/v1/cex/candle", {"error": "boom"}, http_status=500
)
def test_candle_server_error():
with pytest.raises(ServerError):
_client()(exchange="binance", market="spot", symbol="BTC-USDT")
68 changes: 68 additions & 0 deletions tests/test_cex_fee.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
"""Local (mocked) tests for the CexFee client. No API key / network."""

import re
import responses
import pytest
from urllib.parse import urlparse, parse_qs

from datamaxi.datamaxi.cex_fee import CexFee
from datamaxi.error import ClientError, ServerError
from tests.util import mock_http_response

BASE_URL = "https://api.datamaxiplus.com"

_FEE = [{"exchange": "binance", "symbol": "BTC-USDT", "maker": "0.001"}]


def _client():
return CexFee(api_key="key", base_url=BASE_URL)


def _qs(call):
return parse_qs(urlparse(call.request.url).query)


@mock_http_response(responses.GET, "/api/v1/cex/fees", _FEE)
def test_fee_call_returns_list():
res = _client()(exchange="binance", symbol="BTC-USDT")
assert res == _FEE


@responses.activate
def test_fee_call_sends_query_params():
responses.add(
responses.GET,
re.compile(".*/api/v1/cex/fees.*"),
json=_FEE,
status=200,
)
_client()(exchange="binance", symbol="BTC-USDT")
qs = _qs(responses.calls[0])
assert qs["exchange"] == ["binance"]
assert qs["symbol"] == ["BTC-USDT"]


@mock_http_response(responses.GET, "/api/v1/cex/fees/exchanges", ["binance", "okx"])
def test_fee_exchanges_returns_list():
assert _client().exchanges() == ["binance", "okx"]


@mock_http_response(responses.GET, "/api/v1/cex/fees/symbols", ["BTC-USDT", "ETH-USDT"])
def test_fee_symbols_returns_list():
assert _client().symbols(exchange="binance") == ["BTC-USDT", "ETH-USDT"]


@mock_http_response(
responses.GET, "/api/v1/cex/fees", {"error": "bad request"}, http_status=400
)
def test_fee_client_error():
with pytest.raises(ClientError):
_client()(exchange="binance")


@mock_http_response(
responses.GET, "/api/v1/cex/fees", {"error": "boom"}, http_status=500
)
def test_fee_server_error():
with pytest.raises(ServerError):
_client()(exchange="binance")
Loading
Loading