From fb171e765576ece3546cef6632de9129903d13cc Mon Sep 17 00:00:00 2001 From: Martin Kersner Date: Mon, 6 Jul 2026 12:26:34 +0900 Subject: [PATCH 1/3] test(ws): live ticker data assertion on high-traffic channel Sporadic-channel live tests treat a quiet window as pass. Add ticker (BTC-USDT@binance spot) test asserting a real data msg (s==BTC-USDT, has p) arrives within 30s; quiet window is a FAIL. Proves SDK decodes+yields payload end-to-end. integration marker, needs DATAMAXI_API_KEY. --- tests/test_ws_live.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/tests/test_ws_live.py b/tests/test_ws_live.py index d94c59b..49f8496 100644 --- a/tests/test_ws_live.py +++ b/tests/test_ws_live.py @@ -28,6 +28,9 @@ # Sporadic channels may be silent for long stretches; a quiet window is a pass. _QUIET_TIMEOUT = 10.0 +# High-traffic channels emit continuously, so a quiet window is a FAIL. ticker +# ticks roughly every ~10s in practice; give headroom so one slow tick can't flake. +_DATA_TIMEOUT = 30.0 def _run(coro): @@ -76,3 +79,19 @@ async def run(): pytest.skip(f"/announcement/listing rejected (needs Pro+ tier): {exc!r}") if msg is not None: assert isinstance(msg, dict) + + +def test_ws_ticker_yields_live_data(): + # /ticker (spot) is high-traffic: it emits continuously, so a quiet window is + # a FAIL, not a pass. Unlike the sporadic channels above, this proves the SDK + # decodes and yields a real payload end-to-end — connect + auth + SUBSCRIBE + + # an actual well-formed data message for BTC-USDT@binance within the timeout. + async def run(): + async with AsyncDatamaxiWS(api_key=API_KEY, base_url=BASE_URL) as ws: + stream = await ws.ticker.subscribe("BTC-USDT@binance", market="spot") + return await asyncio.wait_for(stream.__anext__(), _DATA_TIMEOUT) + + msg = _run(run()) + assert isinstance(msg, dict) + assert msg["s"] == "BTC-USDT" + assert "p" in msg From ec239f4f9eb17839a5b180c5ddc78629b91a16a2 Mon Sep 17 00:00:00 2001 From: Martin Kersner Date: Mon, 6 Jul 2026 13:22:42 +0900 Subject: [PATCH 2/3] test(ws): live premium data-assert + forex/funding/OI quiet-tolerant lanes premium high-traffic (24/7) -> hard data-assert (key match, premium field present; value nullable so not asserted). forex/funding-rate/open-interest sporadic -> quiet window=PASS, shape-checked when a msg arrives (forex s/r; funding/OI non-empty dict, shape not observable live). Widen module docstring. --- tests/test_ws_live.py | 100 +++++++++++++++++++++++++++++++++++++++--- 1 file changed, 93 insertions(+), 7 deletions(-) diff --git a/tests/test_ws_live.py b/tests/test_ws_live.py index 49f8496..8bc74df 100644 --- a/tests/test_ws_live.py +++ b/tests/test_ws_live.py @@ -1,12 +1,19 @@ -"""Keyed live/integration lane for sporadic WS channels. +"""Keyed live/integration lane for the real DataMaxi+ WS API. The offline `test_ws.py` covers the protocol against a local server. These -tests hit the real DataMaxi+ WS API for the two event-driven channels that -only got ack-level live checks — `/liquidation` (subscribe-only) and -`/announcement/listing` (Pro+). Both are sporadic, so a quiet window (no event -in the timeout) is a PASS: the value is that connect + auth + SUBSCRIBE succeed -without raising. Opt-in and deselected from the keyless CI lane via the -`integration` marker; needs DATAMAXI_API_KEY. Skipped when `websockets` absent. +tests hit prod. Two kinds of channel: + +- Sporadic (event-driven): `/liquidation`, `/announcement/listing`, + `/funding-rate`, `/open-interest`, and `/forex` (weekends/off-hours). A quiet + window (no message in the timeout) is a PASS — the value is that connect + + auth + SUBSCRIBE succeed without raising; when a message does arrive its shape + is still checked. +- High-traffic (continuous): `/ticker` and `/premium`. These emit non-stop, so + a quiet window is a FAIL — the test asserts a real, well-formed data payload + actually arrives, proving the SDK decodes and yields end-to-end. + +Opt-in and deselected from the keyless CI lane via the `integration` marker; +needs DATAMAXI_API_KEY. Skipped when `websockets` absent. """ import asyncio @@ -31,6 +38,9 @@ # High-traffic channels emit continuously, so a quiet window is a FAIL. ticker # ticks roughly every ~10s in practice; give headroom so one slow tick can't flake. _DATA_TIMEOUT = 30.0 +# /forex is quiet-tolerant but its first tick can lag (~14s observed); wait a bit +# longer than _QUIET_TIMEOUT so an arriving tick gets shape-checked, not skipped. +_FOREX_TIMEOUT = 20.0 def _run(coro): @@ -95,3 +105,79 @@ async def run(): assert isinstance(msg, dict) assert msg["s"] == "BTC-USDT" assert "p" in msg + + +def test_ws_premium_yields_live_data(): + # /premium (cross-exchange premium) is high-traffic: crypto premium trades + # 24/7, so — like ticker — a quiet window is a FAIL. Proves the SDK decodes + # and yields a real premium payload end-to-end. The `premium` value itself + # can be null, so assert on stable identity fields, not the value. + key = "binance:upbit:bitcoin:USDT:KRW:spot:spot" + + async def run(): + async with AsyncDatamaxiWS(api_key=API_KEY, base_url=BASE_URL) as ws: + stream = await ws.premium.subscribe(key) + return await asyncio.wait_for(stream.__anext__(), _DATA_TIMEOUT) + + msg = _run(run()) + assert isinstance(msg, dict) + assert msg["key"] == key + assert "premium" in msg + + +def test_ws_forex_tolerates_quiet_window(): + # /forex is continuous during FX market hours but the first tick can lag + # (~14s observed) and FX spot does not trade weekends — so a quiet window is + # a legitimate PASS (like the sporadic channels). When a tick does arrive, + # validate its shape. + async def run(): + async with AsyncDatamaxiWS(api_key=API_KEY, base_url=BASE_URL) as ws: + stream = await ws.forex.subscribe("USD-KRW") + try: + return await asyncio.wait_for(stream.__anext__(), _FOREX_TIMEOUT) + except asyncio.TimeoutError: + # quiet window (off-hours/weekend): subscribed fine, just no tick + return None + + msg = _run(run()) + if msg is not None: + assert isinstance(msg, dict) + assert msg["s"] == "USD-KRW" + assert "r" in msg + + +def test_ws_funding_rate_tolerates_quiet_window(): + # /funding-rate pushes on-change/snapshot — sporadic (0 msgs in a 20s probe), + # so a quiet window is a PASS. Shape wasn't observable live; when a message + # does arrive, assert only that it's a non-empty data dict (acks are already + # filtered by the client's reader). + async def run(): + async with AsyncDatamaxiWS(api_key=API_KEY, base_url=BASE_URL) as ws: + stream = await ws.funding_rate.subscribe("BTC-USDT@binance") + try: + return await asyncio.wait_for(stream.__anext__(), _QUIET_TIMEOUT) + except asyncio.TimeoutError: + return None + + msg = _run(run()) + if msg is not None: + assert isinstance(msg, dict) + assert msg + + +def test_ws_open_interest_tolerates_quiet_window(): + # /open-interest is sporadic (0 msgs in a 20s probe), so a quiet window is a + # PASS. Shape wasn't observable live; when a message does arrive, assert only + # that it's a non-empty data dict (acks are already filtered by the reader). + async def run(): + async with AsyncDatamaxiWS(api_key=API_KEY, base_url=BASE_URL) as ws: + stream = await ws.open_interest.subscribe("BTC-USDT@binance") + try: + return await asyncio.wait_for(stream.__anext__(), _QUIET_TIMEOUT) + except asyncio.TimeoutError: + return None + + msg = _run(run()) + if msg is not None: + assert isinstance(msg, dict) + assert msg From 8b3f89c04ce21e37c603b069085f104722b4b7c0 Mon Sep 17 00:00:00 2001 From: Martin Kersner Date: Mon, 6 Jul 2026 13:35:56 +0900 Subject: [PATCH 3/3] test(ws): live ticker futures data-assert Futures sibling of the spot ticker test; high-traffic -> silence=FAIL. Asserts s==BTC-USDT, has p, and m==futures to prove the futures path. --- tests/test_ws_live.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/tests/test_ws_live.py b/tests/test_ws_live.py index 8bc74df..1fd5927 100644 --- a/tests/test_ws_live.py +++ b/tests/test_ws_live.py @@ -107,6 +107,22 @@ async def run(): assert "p" in msg +def test_ws_ticker_futures_yields_live_data(): + # /ticker/futures is the futures-market sibling of the spot test above: also + # high-traffic, so a quiet window is a FAIL. Assert `m == "futures"` too, to + # prove the futures path (not spot) is what's exercised end-to-end. + async def run(): + async with AsyncDatamaxiWS(api_key=API_KEY, base_url=BASE_URL) as ws: + stream = await ws.ticker.subscribe("BTC-USDT@binance", market="futures") + return await asyncio.wait_for(stream.__anext__(), _DATA_TIMEOUT) + + msg = _run(run()) + assert isinstance(msg, dict) + assert msg["s"] == "BTC-USDT" + assert "p" in msg + assert msg["m"] == "futures" + + def test_ws_premium_yields_live_data(): # /premium (cross-exchange premium) is high-traffic: crypto premium trades # 24/7, so — like ticker — a quiet window is a FAIL. Proves the SDK decodes