Skip to content

Commit febce55

Browse files
arturbegclaude
andcommitted
test(sdk): add offline entry-rule negative tests (PRO-198 P1)
Cover the order/cancel validation gates without a devnet round-trip: - reduce_only rejected on spot and on TP/SL trigger orders - cancel rejected with neither order_id nor client_order_id - cancel accepts both ids and carries both on the wire (server resolves precedence) Complements the existing post_only / GTT / IOC entry-gate guards. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent b5d5df3 commit febce55

1 file changed

Lines changed: 56 additions & 3 deletions

File tree

tests/parity/test_wire_serialization.py

Lines changed: 56 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@
44
Offline (no devnet): builds payloads with a fixed key + a hand-seeded
55
symbol→marketId map, and asserts on the emitted wire shape — numeric fields as
66
plain decimal strings (never scientific notation), the decoupled
7-
``deadline`` / ``expiresAfter`` fields, and the ``postOnly`` flag and its entry
8-
guards.
7+
``deadline`` / ``expiresAfter`` fields, and the order/cancel entry-rule guards
8+
(``reduceOnly``, ``postOnly``, GTT, and the cancel-identifier rules).
99
1010
Regression: the sell-trigger sentinel limit price is ``Decimal("0.000000001")``,
1111
and ``str(Decimal("0.000000001"))`` is ``"1E-9"``. The server's ethers
@@ -23,13 +23,15 @@
2323
from sdk.open_api.models.order_type import OrderType
2424
from sdk.open_api.models.time_in_force import TimeInForce
2525
from sdk.reya_rest_api import ReyaTradingClient
26+
from sdk.reya_rest_api.client import _SPOT_MARKET_ID_OFFSET
2627
from sdk.reya_rest_api.config import TradingConfig
2728
from sdk.reya_rest_api.models.orders import LimitOrderParameters, TriggerOrderParameters
2829

2930
PRIVATE_KEY = "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80"
3031
SIGNER_ADDRESS = "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266"
3132
CHAIN_ID = 89346162
3233
PERP_SYMBOL = "ETHRUSDPERP"
34+
SPOT_SYMBOL = "WETHRUSD" # market_id >= _SPOT_MARKET_ID_OFFSET => spot namespace
3335

3436

3537
@pytest.fixture
@@ -48,7 +50,7 @@ def client() -> ReyaTradingClient:
4850
account_id=12345,
4951
)
5052
c = ReyaTradingClient(config)
51-
c._symbol_to_market_id = {PERP_SYMBOL: 1} # perp core id, unified == raw
53+
c._symbol_to_market_id = {PERP_SYMBOL: 1, SPOT_SYMBOL: _SPOT_MARKET_ID_OFFSET + 1}
5254
c._symbol_to_tick_size = {PERP_SYMBOL: "0.001"} # tick size drives the sell-trigger sentinel
5355
c._initialized = True
5456
return c
@@ -186,3 +188,54 @@ def test_gtt_rejected_pending_offchain(client: ReyaTradingClient) -> None:
186188
time_in_force=TimeInForce.GTT,
187189
)
188190
)
191+
192+
193+
def test_reduce_only_on_spot_rejected(client: ReyaTradingClient) -> None:
194+
"""reduce_only is perp-IOC-only; on a spot order it must be rejected at entry
195+
(the server forbids it on spot), not silently dropped."""
196+
with pytest.raises(ValueError, match="reduce_only is only supported on perp IOC"):
197+
client.build_create_limit_order_payload(
198+
LimitOrderParameters(
199+
symbol=SPOT_SYMBOL,
200+
is_buy=True,
201+
limit_px="3000",
202+
qty="0.01",
203+
time_in_force=TimeInForce.IOC,
204+
reduce_only=True,
205+
)
206+
)
207+
208+
209+
def test_reduce_only_on_trigger_rejected(client: ReyaTradingClient) -> None:
210+
"""reduce_only / close-on-trigger TP/SL isn't supported yet — an explicit
211+
reduce_only on a trigger order is rejected rather than signed + sent."""
212+
with pytest.raises(ValueError, match="reduce_only on TP/SL trigger orders is not supported"):
213+
client.build_create_trigger_order_payload(
214+
TriggerOrderParameters(
215+
symbol=PERP_SYMBOL,
216+
is_buy=False,
217+
qty="0.01",
218+
trigger_px="1000",
219+
trigger_type=OrderType.STOP_LOSS,
220+
reduce_only=True,
221+
)
222+
)
223+
224+
225+
def test_cancel_requires_an_identifier(client: ReyaTradingClient) -> None:
226+
"""A cancel must carry at least one of order_id / client_order_id; neither is rejected."""
227+
with pytest.raises(ValueError, match="Provide either order_id or client_order_id"):
228+
client.build_cancel_order_payload(symbol=PERP_SYMBOL)
229+
230+
231+
def test_cancel_accepts_both_identifiers(client: ReyaTradingClient) -> None:
232+
"""The client accepts both order_id and client_order_id and carries both on the
233+
wire (the server resolves precedence in favour of order_id) rather than rejecting
234+
the combination."""
235+
payload = client.build_cancel_order_payload(
236+
symbol=PERP_SYMBOL,
237+
order_id="123",
238+
client_order_id=456,
239+
)
240+
assert payload["orderId"] == "123"
241+
assert payload["clientOrderId"] == 456

0 commit comments

Comments
 (0)