Files
sanguo_vnpy_v2/tests/trader/test_shadow_orders.py
T
claude_dev 38f5635b59 test(live): D-3持久化测试(bridge_client+影子下单集成, NAS环境11例绿)
- test_bridge_client: to_bridge_code转换(sh/sz/前缀) + HTTP mock(成功/失败返回None不抛)
- test_shadow_orders: 幂等persistence + enabled跳过 + 正向记录+symbol转换 + 幂等不重复 + bridge失败不阻断
- NAS容器真实环境(Python3.10/pytest) 11 passed
- 补 D-3 测试空缺(Sub Agent临时自测未沉淀成持久测试)
2026-07-11 05:35:20 +08:00

101 lines
4.0 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""D-3 影子下单集成测试(persistence 幂等 + _shadow_trades_to_bridge 正向路径)。
不依赖 Windows——mock BridgeClient.place_order,验证:
- persistence 幂等(save_shadow_order / is_trade_shadowed
- enabled=false 跳过(不下单、不查库)
- enabled=true + bridge ok → paper_shadow_orders 记录 + symbol 转换正确
- 幂等:重跑同 trade_id 不重复下单
- bridge 失败 → status=failed,不阻断
"""
import sqlite3
from types import SimpleNamespace
from unittest.mock import patch
from sanguo_trader.live_orchestrator import _shadow_trades_to_bridge
from sanguo_trader.persistence import (
init_db,
is_trade_shadowed,
save_shadow_order,
save_trade,
)
_DATE = "2026-07-11"
def _mk_db(tmp_path) -> str:
db = str(tmp_path / "t.db")
init_db(db)
return db
def _cfg(enabled: bool) -> SimpleNamespace:
return SimpleNamespace(live={
"enabled": enabled, "bridge_url": "http://b.test", "shadow": True,
})
def _insert_trade(db, account_id=1, symbol="600000", direction="buy") -> int:
"""插一条当日成交,返回 trade_id。"""
return save_trade(db, account_id, {
"symbol": symbol, "direction": direction, "price": 10.5,
"volume": 100, "bar_date": _DATE, "strategy_id": "s1",
})
class TestShadowPersistence:
def test_idempotent_roundtrip(self, tmp_path):
db = _mk_db(tmp_path)
assert not is_trade_shadowed(db, 1, 100)
save_shadow_order(db, 1, 100, 555, "ok")
assert is_trade_shadowed(db, 1, 100)
class TestShadowTradesToBridge:
def test_disabled_skips(self, tmp_path):
"""enabled=false → 不创建 BridgeClient、不下单。"""
db = _mk_db(tmp_path)
_insert_trade(db)
with patch("sanguo_trader.bridge_client.BridgeClient") as mbc:
_shadow_trades_to_bridge(db, 1, _DATE, _cfg(False))
mbc.assert_not_called()
def test_enabled_records_and_symbol_converted(self, tmp_path):
"""enabled=true + bridge ok → 记录 paper_shadow_orders + code 转换正确。"""
db = _mk_db(tmp_path)
trade_id = _insert_trade(db, symbol="600000", direction="buy")
with patch.dict("os.environ", {"BRIDGE_TOKEN": "tok"}), \
patch("sanguo_trader.bridge_client.BridgeClient") as mbc:
mbc.return_value.place_order.return_value = {"ok": True, "order_id": 777}
_shadow_trades_to_bridge(db, 1, _DATE, _cfg(True))
call = mbc.return_value.place_order.call_args
assert call[0][0] == "sh600000" # code 已转 sh 前缀
assert call[0][1] == "buy"
assert is_trade_shadowed(db, 1, trade_id)
def test_idempotent_no_duplicate(self, tmp_path):
"""同 trade_id 重跑 → 不重复下单。"""
db = _mk_db(tmp_path)
_insert_trade(db)
with patch.dict("os.environ", {"BRIDGE_TOKEN": "tok"}), \
patch("sanguo_trader.bridge_client.BridgeClient") as mbc:
mbc.return_value.place_order.return_value = {"ok": True, "order_id": 777}
_shadow_trades_to_bridge(db, 1, _DATE, _cfg(True))
assert mbc.return_value.place_order.call_count == 1
# 重跑:trade_id 已 shadowed,不再下单
_shadow_trades_to_bridge(db, 1, _DATE, _cfg(True))
assert mbc.return_value.place_order.call_count == 1
def test_bridge_failure_does_not_block(self, tmp_path):
"""bridge 返回 None(失败)→ 记 status=failed,不抛异常。"""
db = _mk_db(tmp_path)
_insert_trade(db)
with patch.dict("os.environ", {"BRIDGE_TOKEN": "tok"}), \
patch("sanguo_trader.bridge_client.BridgeClient") as mbc:
mbc.return_value.place_order.return_value = None
_shadow_trades_to_bridge(db, 1, _DATE, _cfg(True)) # 不抛
with sqlite3.connect(db) as conn:
row = conn.execute(
"SELECT status FROM paper_shadow_orders WHERE account_id=1"
).fetchone()
assert row and row[0] == "failed"