"""Trade execution tests.""" from __future__ import annotations import unittest from unittest.mock import patch from coinhunter.services import trade_service class FakeSpotClient: def __init__(self): self.calls = [] def new_order(self, **kwargs): self.calls.append(kwargs) return {"symbol": kwargs["symbol"], "status": "FILLED", "orderId": 1} class TradeServiceTestCase(unittest.TestCase): def test_spot_market_buy_dry_run_does_not_call_client(self): events = [] with patch.object( trade_service, "audit_event", side_effect=lambda event, payload: events.append((event, payload)) ): client = FakeSpotClient() payload = trade_service.execute_spot_trade( {"trading": {"dry_run_default": False}}, side="buy", symbol="btc/usdt", qty=None, quote=100, order_type="market", price=None, dry_run=True, spot_client=client, ) self.assertEqual(payload["trade"]["status"], "DRY_RUN") self.assertEqual(client.calls, []) self.assertEqual([event for event, _ in events], ["trade_submitted", "trade_filled"]) def test_spot_limit_sell_maps_payload(self): with patch.object(trade_service, "audit_event", return_value=None): client = FakeSpotClient() payload = trade_service.execute_spot_trade( {"trading": {"dry_run_default": False}}, side="sell", symbol="BTCUSDT", qty=0.1, quote=None, order_type="limit", price=90000, dry_run=False, spot_client=client, ) self.assertEqual(payload["trade"]["status"], "FILLED") self.assertEqual(client.calls[0]["timeInForce"], "GTC") def test_spot_market_buy_requires_quote(self): with ( patch.object(trade_service, "audit_event", return_value=None), self.assertRaisesRegex(RuntimeError, "requires --quote"), ): trade_service.execute_spot_trade( {"trading": {"dry_run_default": False}}, side="buy", symbol="BTCUSDT", qty=None, quote=None, order_type="market", price=None, dry_run=False, spot_client=FakeSpotClient(), ) def test_spot_market_buy_rejects_qty(self): with ( patch.object(trade_service, "audit_event", return_value=None), self.assertRaisesRegex(RuntimeError, "accepts --quote only"), ): trade_service.execute_spot_trade( {"trading": {"dry_run_default": False}}, side="buy", symbol="BTCUSDT", qty=0.1, quote=100, order_type="market", price=None, dry_run=False, spot_client=FakeSpotClient(), ) def test_spot_market_sell_rejects_quote(self): with ( patch.object(trade_service, "audit_event", return_value=None), self.assertRaisesRegex(RuntimeError, "accepts --qty only"), ): trade_service.execute_spot_trade( {"trading": {"dry_run_default": False}}, side="sell", symbol="BTCUSDT", qty=0.1, quote=100, order_type="market", price=None, dry_run=False, spot_client=FakeSpotClient(), )