|
|
@@ -32,6 +32,7 @@ class RecordedRequest:
|
|
|
params: dict[str, object] | None
|
|
|
json_body: dict[str, object] | None
|
|
|
body: str | None
|
|
|
+ timeout: float | None
|
|
|
|
|
|
|
|
|
class DummySession:
|
|
|
@@ -58,6 +59,7 @@ class DummySession:
|
|
|
params: dict[str, object] | None = None,
|
|
|
json: dict[str, object] | None = None,
|
|
|
data: str | None = None,
|
|
|
+ timeout: float | None = None,
|
|
|
) -> DummyResponse:
|
|
|
parsed_json = json
|
|
|
if parsed_json is None and data is not None:
|
|
|
@@ -69,6 +71,7 @@ class DummySession:
|
|
|
params=params,
|
|
|
json_body=parsed_json,
|
|
|
body=data,
|
|
|
+ timeout=timeout,
|
|
|
)
|
|
|
self.request_paths.append(urlparse(url).path)
|
|
|
self.request_bodies.append(parsed_json)
|
|
|
@@ -336,6 +339,23 @@ def positions_with_non_finite_numeric_response() -> DummyResponse:
|
|
|
)
|
|
|
|
|
|
|
|
|
+def positions_with_wrong_symbol_response() -> DummyResponse:
|
|
|
+ return DummyResponse(
|
|
|
+ {
|
|
|
+ "code": "0",
|
|
|
+ "msg": "",
|
|
|
+ "data": [
|
|
|
+ {
|
|
|
+ "instId": "ETH-USDT-SWAP",
|
|
|
+ "posSide": "long",
|
|
|
+ "pos": "1",
|
|
|
+ "avgPx": "25000",
|
|
|
+ }
|
|
|
+ ],
|
|
|
+ }
|
|
|
+ )
|
|
|
+
|
|
|
+
|
|
|
def market_long_signal() -> TradeSignal:
|
|
|
return TradeSignal(
|
|
|
action="long",
|
|
|
@@ -395,6 +415,8 @@ def test_signed_demo_request_attaches_headers():
|
|
|
).digest()
|
|
|
).decode()
|
|
|
assert request.headers["OK-ACCESS-SIGN"] == expected_signature
|
|
|
+ assert request.timeout is not None
|
|
|
+ assert request.timeout > 0
|
|
|
|
|
|
|
|
|
def test_signed_post_request_uses_actual_serialized_body_bytes():
|
|
|
@@ -678,6 +700,14 @@ def test_get_last_price_rejects_non_finite_numeric_field():
|
|
|
client.get_last_price(symbol="BTC-USDT-SWAP")
|
|
|
|
|
|
|
|
|
+def test_get_last_price_rejects_mismatched_symbol():
|
|
|
+ session = DummySession([DummyResponse({"code": "0", "msg": "", "data": [{"instId": "ETH-USDT-SWAP", "last": "25000"}]})])
|
|
|
+ client = OkxClient(config=sample_config(), session=session)
|
|
|
+
|
|
|
+ with pytest.raises(ValueError, match="okx response payload is invalid"):
|
|
|
+ client.get_last_price(symbol="BTC-USDT-SWAP")
|
|
|
+
|
|
|
+
|
|
|
def test_get_instrument_meta_rejects_mismatched_symbol():
|
|
|
session = DummySession([instrument_with_wrong_symbol_response()])
|
|
|
client = OkxClient(config=sample_config(), session=session)
|
|
|
@@ -728,6 +758,16 @@ def test_place_demo_order_rejects_invalid_leverage_before_okx():
|
|
|
assert session.request_paths == []
|
|
|
|
|
|
|
|
|
+@pytest.mark.parametrize("margin_usdt", [0, -1, float("nan"), float("inf")])
|
|
|
+def test_place_demo_order_rejects_invalid_margin_before_okx(margin_usdt):
|
|
|
+ session = DummySession([])
|
|
|
+ client = OkxClient(config=sample_config(), session=session)
|
|
|
+
|
|
|
+ with pytest.raises(ValueError, match="margin_usdt is invalid"):
|
|
|
+ client.place_demo_order(symbol="BTC-USDT-SWAP", signal=market_long_signal(), margin_usdt=margin_usdt)
|
|
|
+ assert session.request_paths == []
|
|
|
+
|
|
|
+
|
|
|
@pytest.mark.parametrize(
|
|
|
("symbol", "leverage", "pos_side", "expected_message"),
|
|
|
[
|
|
|
@@ -811,3 +851,11 @@ def test_get_positions_rejects_non_finite_numeric_fields():
|
|
|
|
|
|
with pytest.raises(ValueError, match="okx response payload is invalid"):
|
|
|
client.get_positions(symbol="BTC-USDT-SWAP")
|
|
|
+
|
|
|
+
|
|
|
+def test_get_positions_rejects_mismatched_symbol():
|
|
|
+ session = DummySession([positions_with_wrong_symbol_response()])
|
|
|
+ client = OkxClient(config=sample_config(), session=session)
|
|
|
+
|
|
|
+ with pytest.raises(ValueError, match="okx response payload is invalid"):
|
|
|
+ client.get_positions(symbol="BTC-USDT-SWAP")
|