|
14 | 14 | websockets = pytest.importorskip("websockets") |
15 | 15 |
|
16 | 16 | import datamaxi._ws_models as _ws_models # noqa: E402 |
17 | | -from datamaxi.aio.ws import AsyncDatamaxiWS # noqa: E402 |
| 17 | +from datamaxi.aio.ws import AsyncDatamaxiWS, build_param # noqa: E402 |
18 | 18 | from datamaxi._ws_endpoints import WS_CHANNELS, WS_BASE_PATH # noqa: E402 |
19 | 19 | from datamaxi._ws_models import TickerMessage # noqa: E402 |
20 | 20 |
|
@@ -281,3 +281,150 @@ async def run(): |
281 | 281 | assert len(seen) >= 2 |
282 | 282 | assert seen[0] == ["USD-KRW"] |
283 | 283 | assert seen[1] == ["USD-KRW"] # `_open` replays sorted(self._active) |
| 284 | + |
| 285 | + |
| 286 | +# --- structured subscribe helpers: build_param unit tests --- |
| 287 | + |
| 288 | + |
| 289 | +def test_build_param_forex_single_token(): |
| 290 | + # SYMBOL: no separator, one token; and the SYMBOL->symbol lowercase rule. |
| 291 | + assert build_param("SYMBOL", symbol="USD-KRW") == "USD-KRW" |
| 292 | + |
| 293 | + |
| 294 | +def test_build_param_symbol_exchange(): |
| 295 | + assert ( |
| 296 | + build_param("SYMBOL@exchange", symbol="BTC-USDT", exchange="binance") |
| 297 | + == "BTC-USDT@binance" |
| 298 | + ) |
| 299 | + |
| 300 | + |
| 301 | +def test_build_param_ticker_without_optional_group(): |
| 302 | + fmt = "SYMBOL@exchange[@currency@conversionBase]" |
| 303 | + assert build_param(fmt, symbol="BTC-USDT", exchange="binance") == "BTC-USDT@binance" |
| 304 | + |
| 305 | + |
| 306 | +def test_build_param_ticker_with_optional_group(): |
| 307 | + fmt = "SYMBOL@exchange[@currency@conversionBase]" |
| 308 | + assert ( |
| 309 | + build_param( |
| 310 | + fmt, |
| 311 | + symbol="BTC-USDT", |
| 312 | + exchange="binance", |
| 313 | + currency="KRW", |
| 314 | + conversionBase="USDT", |
| 315 | + ) |
| 316 | + == "BTC-USDT@binance@KRW@USDT" |
| 317 | + ) |
| 318 | + |
| 319 | + |
| 320 | +def test_build_param_premium_all_seven_tokens(): |
| 321 | + fmt = "src:tgt:tokenId:srcQuote:tgtQuote:srcMkt:tgtMkt" |
| 322 | + assert ( |
| 323 | + build_param( |
| 324 | + fmt, |
| 325 | + src="binance", |
| 326 | + tgt="upbit", |
| 327 | + tokenId="bitcoin", |
| 328 | + srcQuote="USDT", |
| 329 | + tgtQuote="KRW", |
| 330 | + srcMkt="spot", |
| 331 | + tgtMkt="spot", |
| 332 | + ) |
| 333 | + == "binance:upbit:bitcoin:USDT:KRW:spot:spot" |
| 334 | + ) |
| 335 | + |
| 336 | + |
| 337 | +def test_build_param_verbatim_token_not_lowercased(): |
| 338 | + # tokenId is mixed-case, so it stays verbatim (not lowercased). |
| 339 | + with pytest.raises(ValueError): |
| 340 | + build_param( |
| 341 | + "src:tgt:tokenId:srcQuote:tgtQuote:srcMkt:tgtMkt", |
| 342 | + src="a", |
| 343 | + tgt="b", |
| 344 | + tokenid="c", # wrong: lowercased key |
| 345 | + srcQuote="d", |
| 346 | + tgtQuote="e", |
| 347 | + srcMkt="f", |
| 348 | + tgtMkt="g", |
| 349 | + ) |
| 350 | + |
| 351 | + |
| 352 | +def test_build_param_none_channel_rejects_tokens(): |
| 353 | + with pytest.raises(ValueError): |
| 354 | + build_param(None, symbol="X") |
| 355 | + |
| 356 | + |
| 357 | +def test_build_param_missing_required_token(): |
| 358 | + with pytest.raises(ValueError) as ei: |
| 359 | + build_param("SYMBOL@exchange", symbol="BTC-USDT") |
| 360 | + assert "exchange" in str(ei.value) |
| 361 | + |
| 362 | + |
| 363 | +def test_build_param_unknown_token(): |
| 364 | + with pytest.raises(ValueError) as ei: |
| 365 | + build_param("SYMBOL@exchange", symbol="BTC-USDT", exchange="binance", bogus="x") |
| 366 | + assert "bogus" in str(ei.value) |
| 367 | + |
| 368 | + |
| 369 | +def test_build_param_partial_optional_group_rejected(): |
| 370 | + fmt = "SYMBOL@exchange[@currency@conversionBase]" |
| 371 | + with pytest.raises(ValueError): |
| 372 | + build_param(fmt, symbol="BTC-USDT", exchange="binance", currency="KRW") |
| 373 | + |
| 374 | + |
| 375 | +def test_build_param_roundtrip_matches_raw_ticker(): |
| 376 | + fmt = WS_CHANNELS["/ticker/spot"]["param"] |
| 377 | + assert build_param(fmt, symbol="BTC-USDT", exchange="binance") == "BTC-USDT@binance" |
| 378 | + |
| 379 | + |
| 380 | +def test_build_param_roundtrip_matches_raw_premium(): |
| 381 | + fmt = WS_CHANNELS["/premium"]["param"] |
| 382 | + assert ( |
| 383 | + build_param( |
| 384 | + fmt, |
| 385 | + src="binance", |
| 386 | + tgt="upbit", |
| 387 | + tokenId="bitcoin", |
| 388 | + srcQuote="USDT", |
| 389 | + tgtQuote="KRW", |
| 390 | + srcMkt="spot", |
| 391 | + tgtMkt="spot", |
| 392 | + ) |
| 393 | + == "binance:upbit:bitcoin:USDT:KRW:spot:spot" |
| 394 | + ) |
| 395 | + |
| 396 | + |
| 397 | +def test_ws_structured_subscribe_produces_expected_wire_param(): |
| 398 | + # A structured subscribe(...) call sends the same wire param as the raw form. |
| 399 | + async def handler(conn): |
| 400 | + async for raw in conn: |
| 401 | + m = json.loads(raw) |
| 402 | + if m.get("method") == "SUBSCRIBE": |
| 403 | + await conn.send(json.dumps({"result": m["params"], "id": m["id"]})) |
| 404 | + for p in m["params"]: |
| 405 | + sym, exch = p.split("@")[0], p.split("@")[1] |
| 406 | + await conn.send( |
| 407 | + json.dumps({"s": sym, "e": exch, "p": 1.0, "d": 1, "_param": p}) |
| 408 | + ) |
| 409 | + |
| 410 | + async def run(): |
| 411 | + async with _serve(handler) as server: |
| 412 | + async with AsyncDatamaxiWS( |
| 413 | + api_key="k", ws_url=f"ws://localhost:{_port(server)}" |
| 414 | + ) as ws: |
| 415 | + stream = await ws.ticker.subscribe( |
| 416 | + symbol="BTC-USDT", exchange="binance", market="spot" |
| 417 | + ) |
| 418 | + return await _first(stream) |
| 419 | + |
| 420 | + msg = _run(run()) |
| 421 | + assert msg["_param"] == "BTC-USDT@binance" |
| 422 | + |
| 423 | + |
| 424 | +def test_ws_structured_subscribe_mixing_raw_and_tokens_raises(): |
| 425 | + async def run(): |
| 426 | + async with AsyncDatamaxiWS(api_key="k", ws_url="ws://localhost:1") as ws: |
| 427 | + await ws.forex.subscribe("USD-KRW", symbol="USD-KRW") |
| 428 | + |
| 429 | + with pytest.raises(ValueError): |
| 430 | + _run(run()) |
0 commit comments