|
@@ -1,6 +1,7 @@
|
|
|
import base64
|
|
import base64
|
|
|
import hashlib
|
|
import hashlib
|
|
|
import hmac
|
|
import hmac
|
|
|
|
|
+import json as json_module
|
|
|
from dataclasses import dataclass
|
|
from dataclasses import dataclass
|
|
|
from urllib.parse import urlencode, urlparse
|
|
from urllib.parse import urlencode, urlparse
|
|
|
|
|
|
|
@@ -30,6 +31,7 @@ class RecordedRequest:
|
|
|
headers: dict[str, str]
|
|
headers: dict[str, str]
|
|
|
params: dict[str, object] | None
|
|
params: dict[str, object] | None
|
|
|
json_body: dict[str, object] | None
|
|
json_body: dict[str, object] | None
|
|
|
|
|
+ body: str | None
|
|
|
|
|
|
|
|
|
|
|
|
|
class DummySession:
|
|
class DummySession:
|
|
@@ -43,6 +45,10 @@ class DummySession:
|
|
|
def last_json_body(self) -> dict[str, object] | None:
|
|
def last_json_body(self) -> dict[str, object] | None:
|
|
|
return self.last_request.json_body if self.last_request else None
|
|
return self.last_request.json_body if self.last_request else None
|
|
|
|
|
|
|
|
|
|
+ @property
|
|
|
|
|
+ def last_body(self) -> str | None:
|
|
|
|
|
+ return self.last_request.body if self.last_request else None
|
|
|
|
|
+
|
|
|
def request(
|
|
def request(
|
|
|
self,
|
|
self,
|
|
|
method: str,
|
|
method: str,
|
|
@@ -51,16 +57,21 @@ class DummySession:
|
|
|
headers: dict[str, str] | None = None,
|
|
headers: dict[str, str] | None = None,
|
|
|
params: dict[str, object] | None = None,
|
|
params: dict[str, object] | None = None,
|
|
|
json: dict[str, object] | None = None,
|
|
json: dict[str, object] | None = None,
|
|
|
|
|
+ data: str | None = None,
|
|
|
) -> DummyResponse:
|
|
) -> DummyResponse:
|
|
|
|
|
+ parsed_json = json
|
|
|
|
|
+ if parsed_json is None and data is not None:
|
|
|
|
|
+ parsed_json = json_module.loads(data)
|
|
|
self.last_request = RecordedRequest(
|
|
self.last_request = RecordedRequest(
|
|
|
method=method,
|
|
method=method,
|
|
|
url=url,
|
|
url=url,
|
|
|
headers=headers or {},
|
|
headers=headers or {},
|
|
|
params=params,
|
|
params=params,
|
|
|
- json_body=json,
|
|
|
|
|
|
|
+ json_body=parsed_json,
|
|
|
|
|
+ body=data,
|
|
|
)
|
|
)
|
|
|
self.request_paths.append(urlparse(url).path)
|
|
self.request_paths.append(urlparse(url).path)
|
|
|
- self.request_bodies.append(json)
|
|
|
|
|
|
|
+ self.request_bodies.append(parsed_json)
|
|
|
if self._responses:
|
|
if self._responses:
|
|
|
response = self._responses.pop(0)
|
|
response = self._responses.pop(0)
|
|
|
if isinstance(response, Exception):
|
|
if isinstance(response, Exception):
|
|
@@ -299,6 +310,35 @@ def test_signed_demo_request_attaches_headers():
|
|
|
assert request.headers["OK-ACCESS-SIGN"] == expected_signature
|
|
assert request.headers["OK-ACCESS-SIGN"] == expected_signature
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
+def test_signed_post_request_uses_actual_serialized_body_bytes():
|
|
|
|
|
+ session = DummySession(
|
|
|
|
|
+ [
|
|
|
|
|
+ instrument_response(),
|
|
|
|
|
+ account_config_response(pos_mode="long_short_mode"),
|
|
|
|
|
+ leverage_response(),
|
|
|
|
|
+ place_order_response(),
|
|
|
|
|
+ ]
|
|
|
|
|
+ )
|
|
|
|
|
+ client = OkxClient(config=sample_config(), session=session)
|
|
|
|
|
+
|
|
|
|
|
+ client.place_demo_order(symbol="ETH-USDT-SWAP", signal=limit_short_signal(), margin_usdt=100)
|
|
|
|
|
+
|
|
|
|
|
+ request = session.last_request
|
|
|
|
|
+ assert request is not None
|
|
|
|
|
+ assert request.method == "POST"
|
|
|
|
|
+ assert request.body is not None
|
|
|
|
|
+ timestamp = request.headers["OK-ACCESS-TIMESTAMP"]
|
|
|
|
|
+ path = urlparse(request.url).path
|
|
|
|
|
+ expected_signature = base64.b64encode(
|
|
|
|
|
+ hmac.new(
|
|
|
|
|
+ b"secret",
|
|
|
|
|
+ f"{timestamp}{request.method}{path}{request.body}".encode(),
|
|
|
|
|
+ hashlib.sha256,
|
|
|
|
|
+ ).digest()
|
|
|
|
|
+ ).decode()
|
|
|
|
|
+ assert request.headers["OK-ACCESS-SIGN"] == expected_signature
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
def test_get_candles_returns_chronological_ascending_order():
|
|
def test_get_candles_returns_chronological_ascending_order():
|
|
|
session = DummySession([descending_candles_response()])
|
|
session = DummySession([descending_candles_response()])
|
|
|
client = OkxClient(config=sample_config(), session=session)
|
|
client = OkxClient(config=sample_config(), session=session)
|