Files
coinhunter-cli/tests/test_trade_service.py
Tacit Lab d629c25232 fix: resolve merge conflicts and lint issues
- Merge origin/main changes (flattened buy/sell commands, --doc flag, aliases)
- Fix spinner placement for buy/sell commands
- Fix duplicate alias key 'p' in canonical subcommands
- Remove unused mypy ignore comments in spot_client.py
- Fix nested with statements in tests

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-17 16:59:53 +08:00

109 lines
3.8 KiB
Python

"""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(),
)