Skip to content

Commit ec239f4

Browse files
committed
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.
1 parent fb171e7 commit ec239f4

1 file changed

Lines changed: 93 additions & 7 deletions

File tree

‎tests/test_ws_live.py‎

Lines changed: 93 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,19 @@
1-
"""Keyed live/integration lane for sporadic WS channels.
1+
"""Keyed live/integration lane for the real DataMaxi+ WS API.
22
33
The offline `test_ws.py` covers the protocol against a local server. These
4-
tests hit the real DataMaxi+ WS API for the two event-driven channels that
5-
only got ack-level live checks — `/liquidation` (subscribe-only) and
6-
`/announcement/listing` (Pro+). Both are sporadic, so a quiet window (no event
7-
in the timeout) is a PASS: the value is that connect + auth + SUBSCRIBE succeed
8-
without raising. Opt-in and deselected from the keyless CI lane via the
9-
`integration` marker; needs DATAMAXI_API_KEY. Skipped when `websockets` absent.
4+
tests hit prod. Two kinds of channel:
5+
6+
- Sporadic (event-driven): `/liquidation`, `/announcement/listing`,
7+
`/funding-rate`, `/open-interest`, and `/forex` (weekends/off-hours). A quiet
8+
window (no message in the timeout) is a PASS — the value is that connect +
9+
auth + SUBSCRIBE succeed without raising; when a message does arrive its shape
10+
is still checked.
11+
- High-traffic (continuous): `/ticker` and `/premium`. These emit non-stop, so
12+
a quiet window is a FAIL — the test asserts a real, well-formed data payload
13+
actually arrives, proving the SDK decodes and yields end-to-end.
14+
15+
Opt-in and deselected from the keyless CI lane via the `integration` marker;
16+
needs DATAMAXI_API_KEY. Skipped when `websockets` absent.
1017
"""
1118

1219
import asyncio
@@ -31,6 +38,9 @@
3138
# High-traffic channels emit continuously, so a quiet window is a FAIL. ticker
3239
# ticks roughly every ~10s in practice; give headroom so one slow tick can't flake.
3340
_DATA_TIMEOUT = 30.0
41+
# /forex is quiet-tolerant but its first tick can lag (~14s observed); wait a bit
42+
# longer than _QUIET_TIMEOUT so an arriving tick gets shape-checked, not skipped.
43+
_FOREX_TIMEOUT = 20.0
3444

3545

3646
def _run(coro):
@@ -95,3 +105,79 @@ async def run():
95105
assert isinstance(msg, dict)
96106
assert msg["s"] == "BTC-USDT"
97107
assert "p" in msg
108+
109+
110+
def test_ws_premium_yields_live_data():
111+
# /premium (cross-exchange premium) is high-traffic: crypto premium trades
112+
# 24/7, so — like ticker — a quiet window is a FAIL. Proves the SDK decodes
113+
# and yields a real premium payload end-to-end. The `premium` value itself
114+
# can be null, so assert on stable identity fields, not the value.
115+
key = "binance:upbit:bitcoin:USDT:KRW:spot:spot"
116+
117+
async def run():
118+
async with AsyncDatamaxiWS(api_key=API_KEY, base_url=BASE_URL) as ws:
119+
stream = await ws.premium.subscribe(key)
120+
return await asyncio.wait_for(stream.__anext__(), _DATA_TIMEOUT)
121+
122+
msg = _run(run())
123+
assert isinstance(msg, dict)
124+
assert msg["key"] == key
125+
assert "premium" in msg
126+
127+
128+
def test_ws_forex_tolerates_quiet_window():
129+
# /forex is continuous during FX market hours but the first tick can lag
130+
# (~14s observed) and FX spot does not trade weekends — so a quiet window is
131+
# a legitimate PASS (like the sporadic channels). When a tick does arrive,
132+
# validate its shape.
133+
async def run():
134+
async with AsyncDatamaxiWS(api_key=API_KEY, base_url=BASE_URL) as ws:
135+
stream = await ws.forex.subscribe("USD-KRW")
136+
try:
137+
return await asyncio.wait_for(stream.__anext__(), _FOREX_TIMEOUT)
138+
except asyncio.TimeoutError:
139+
# quiet window (off-hours/weekend): subscribed fine, just no tick
140+
return None
141+
142+
msg = _run(run())
143+
if msg is not None:
144+
assert isinstance(msg, dict)
145+
assert msg["s"] == "USD-KRW"
146+
assert "r" in msg
147+
148+
149+
def test_ws_funding_rate_tolerates_quiet_window():
150+
# /funding-rate pushes on-change/snapshot — sporadic (0 msgs in a 20s probe),
151+
# so a quiet window is a PASS. Shape wasn't observable live; when a message
152+
# does arrive, assert only that it's a non-empty data dict (acks are already
153+
# filtered by the client's reader).
154+
async def run():
155+
async with AsyncDatamaxiWS(api_key=API_KEY, base_url=BASE_URL) as ws:
156+
stream = await ws.funding_rate.subscribe("BTC-USDT@binance")
157+
try:
158+
return await asyncio.wait_for(stream.__anext__(), _QUIET_TIMEOUT)
159+
except asyncio.TimeoutError:
160+
return None
161+
162+
msg = _run(run())
163+
if msg is not None:
164+
assert isinstance(msg, dict)
165+
assert msg
166+
167+
168+
def test_ws_open_interest_tolerates_quiet_window():
169+
# /open-interest is sporadic (0 msgs in a 20s probe), so a quiet window is a
170+
# PASS. Shape wasn't observable live; when a message does arrive, assert only
171+
# that it's a non-empty data dict (acks are already filtered by the reader).
172+
async def run():
173+
async with AsyncDatamaxiWS(api_key=API_KEY, base_url=BASE_URL) as ws:
174+
stream = await ws.open_interest.subscribe("BTC-USDT@binance")
175+
try:
176+
return await asyncio.wait_for(stream.__anext__(), _QUIET_TIMEOUT)
177+
except asyncio.TimeoutError:
178+
return None
179+
180+
msg = _run(run())
181+
if msg is not None:
182+
assert isinstance(msg, dict)
183+
assert msg

0 commit comments

Comments
 (0)