feat(trader): live bridge token 改从 config 读取,免容器重建

容器 docker run(非 compose)注入 BRIDGE_TOKEN env 需重建容器,风险大。改为 live_cfg.bridge_token 优先、fallback BRIDGE_TOKEN env。run_live_step 每次 load_config,改 config 免重启即生效。

- _shadow_trades_to_bridge + reconcile_from_bridge 两处 token 读取
- config: live.bridge_token 占位空值(真实值填 NAS gitignored config,不入库)
- tests: +6 测试(config优先/env fallback/都无跳过),191 passed
This commit is contained in:
2026-07-13 19:30:58 +08:00
parent c041227139
commit 0761342baf
4 changed files with 102 additions and 5 deletions
+52
View File
@@ -142,3 +142,55 @@ class TestReconcile:
patch("sanguo_trader.bridge_client.BridgeClient") as mbc:
mbc.return_value.get_account.side_effect = RuntimeError("boom")
reconcile_from_bridge(db, 1, _DATE, acc, _cfg(True)) # 不抛
class TestReconcileTokenSource:
"""bridge_token 优先级:config > env > skipreconcile 模式 B)。"""
@staticmethod
def _ok_account_resp():
return {"ok": True, "cash": 80000.0, "frozen": 0,
"market_value": 0.0, "total": 80000.0}
def test_config_token_used(self, tmp_path):
"""(a) config 有 bridge_token → 用 config tokenenv 未设)。"""
db = _mk_db(tmp_path)
acc = Account(100000.0)
cfg = SimpleNamespace(live={
"enabled": True, "bridge_url": "http://b.test",
"shadow": True, "mode_b": True, "bridge_token": "cfg-token",
})
with patch.dict("os.environ", {}, clear=True), \
patch("sanguo_trader.bridge_client.BridgeClient") as mbc:
mbc.return_value.get_account.return_value = self._ok_account_resp()
mbc.return_value.get_positions.return_value = []
reconcile_from_bridge(db, 1, _DATE, acc, cfg)
assert mbc.call_args[0][1] == "cfg-token" # BridgeClient(url, token)
def test_env_fallback_when_config_missing(self, tmp_path):
"""(b) config 无 bridge_token → fallback 到 BRIDGE_TOKEN env。"""
db = _mk_db(tmp_path)
acc = Account(100000.0)
cfg = SimpleNamespace(live={
"enabled": True, "bridge_url": "http://b.test",
"shadow": True, "mode_b": True,
})
with patch.dict("os.environ", {"BRIDGE_TOKEN": "env-token"}), \
patch("sanguo_trader.bridge_client.BridgeClient") as mbc:
mbc.return_value.get_account.return_value = self._ok_account_resp()
mbc.return_value.get_positions.return_value = []
reconcile_from_bridge(db, 1, _DATE, acc, cfg)
assert mbc.call_args[0][1] == "env-token"
def test_no_token_skips(self, tmp_path):
"""(c) config 和 env 都无 bridge_token → 跳过、warning。"""
db = _mk_db(tmp_path)
acc = Account(100000.0)
cfg = SimpleNamespace(live={
"enabled": True, "bridge_url": "http://b.test",
"shadow": True, "mode_b": True,
})
with patch.dict("os.environ", {}, clear=True), \
patch("sanguo_trader.bridge_client.BridgeClient") as mbc:
reconcile_from_bridge(db, 1, _DATE, acc, cfg)
mbc.assert_not_called()
+43
View File
@@ -98,3 +98,46 @@ class TestShadowTradesToBridge:
"SELECT status FROM paper_shadow_orders WHERE account_id=1"
).fetchone()
assert row and row[0] == "failed"
class TestShadowTokenSource:
"""bridge_token 优先级:config > env > skipD-3 影子下单)。"""
def test_config_token_used(self, tmp_path):
"""(a) config 有 bridge_token → 用 config tokenenv 未设)。"""
db = _mk_db(tmp_path)
_insert_trade(db)
cfg = SimpleNamespace(live={
"enabled": True, "bridge_url": "http://b.test", "shadow": True,
"bridge_token": "cfg-token",
})
with patch.dict("os.environ", {}, clear=True), \
patch("sanguo_trader.bridge_client.BridgeClient") as mbc:
mbc.return_value.place_order.return_value = {"ok": True, "order_id": 1}
_shadow_trades_to_bridge(db, 1, _DATE, cfg)
assert mbc.call_args[0][1] == "cfg-token" # BridgeClient(url, token)
def test_env_fallback_when_config_missing(self, tmp_path):
"""(b) config 无 bridge_token → fallback 到 BRIDGE_TOKEN env。"""
db = _mk_db(tmp_path)
_insert_trade(db)
cfg = SimpleNamespace(live={
"enabled": True, "bridge_url": "http://b.test", "shadow": True,
})
with patch.dict("os.environ", {"BRIDGE_TOKEN": "env-token"}), \
patch("sanguo_trader.bridge_client.BridgeClient") as mbc:
mbc.return_value.place_order.return_value = {"ok": True, "order_id": 1}
_shadow_trades_to_bridge(db, 1, _DATE, cfg)
assert mbc.call_args[0][1] == "env-token"
def test_no_token_skips(self, tmp_path):
"""(c) config 和 env 都无 bridge_token → 跳过、warning,不下单。"""
db = _mk_db(tmp_path)
_insert_trade(db)
cfg = SimpleNamespace(live={
"enabled": True, "bridge_url": "http://b.test", "shadow": True,
})
with patch.dict("os.environ", {}, clear=True), \
patch("sanguo_trader.bridge_client.BridgeClient") as mbc:
_shadow_trades_to_bridge(db, 1, _DATE, cfg)
mbc.assert_not_called()