feat(slots): additive strategy fields + filter (REQ-116, BAT-055)
trex.slots.req wire 계약 additive 확장 — 기존 소비자(orb/tsl) backward compatible. - PortfolioItem: strategy_name(Optional[str]) + markets(list) 필드 추가 - hub_read.py: get_portfolios()에서 strategy_name/markets 파싱 추가 (safe default) - slot_to_dict(): strategy_name/strategy_params/markets 3필드 row에 추가 (SnapshotStore→PortfolioItem 경유) - make_slots_rpc_handler(): strategy 요청 파라미터 → Python 레벨 후처리 필터 (available_slots 계산 이후, REQ-073 AC-6 불변) - 테스트: AC-1~8 전수 + hub_read 파싱 + 회귀 갱신 (777 passed, 47 skipped) - docs/interface-contract.md: 요청 파라미터·응답 행 스키마·소스 테이블·REQ-074 AC-5 주석 갱신 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VHtM5Ws1HKCKQ4JGi6GRb5
This commit is contained in:
@@ -65,6 +65,9 @@ def test_get_portfolios_parses_envelope_and_filters_by_daemon():
|
||||
# strategy fields absent on PortfolioItem -> graceful defaults
|
||||
assert items[0].active_strategy_id is None
|
||||
assert items[0].strategy_params == {}
|
||||
# REQ-116 (BAT-055): safe defaults when hub response has no strategy_name/markets
|
||||
assert items[0].strategy_name is None
|
||||
assert items[0].markets == []
|
||||
# no price field on the domain item
|
||||
for it in items:
|
||||
assert not hasattr(it, "price")
|
||||
@@ -380,3 +383,72 @@ def test_fetch_broker_credential_app_secret_not_in_logs(caplog):
|
||||
full_log = caplog.text
|
||||
# app_secret value must not appear in any log output
|
||||
assert "secret-value" not in full_log
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# REQ-116 (BAT-055): strategy_name + markets parsing in get_portfolios
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def test_get_portfolios_parses_strategy_name_and_markets():
|
||||
"""REQ-116 AC-1: hub provides strategy_name/markets → PortfolioItem carries them."""
|
||||
def handler(req: httpx.Request) -> httpx.Response:
|
||||
return httpx.Response(200, json={"portfolios": [
|
||||
{"portfolio_id": "pf-1", "symbol": "005930",
|
||||
"cash_balance": 1_000_000, "asset_quantity": 0,
|
||||
"managed_by_daemon_id": "d-7",
|
||||
"strategy_name": "tvr_v1",
|
||||
"strategy_params": {"target_weight": 0.05},
|
||||
"markets": ["KRX"]},
|
||||
], "total": 1})
|
||||
|
||||
adapter = PortfolioHubReadAdapter("https://pf.test", token="tok",
|
||||
client=_client(handler))
|
||||
items = adapter.get_portfolios("d-7")
|
||||
assert len(items) == 1
|
||||
it = items[0]
|
||||
assert it.strategy_name == "tvr_v1"
|
||||
assert it.strategy_params == {"target_weight": 0.05}
|
||||
assert it.markets == ["KRX"]
|
||||
|
||||
|
||||
def test_get_portfolios_strategy_name_empty_string_becomes_none():
|
||||
"""REQ-116 AC-2: empty string strategy_name → None."""
|
||||
def handler(req: httpx.Request) -> httpx.Response:
|
||||
return httpx.Response(200, json={"portfolios": [
|
||||
{"portfolio_id": "pf-1", "symbol": "005930",
|
||||
"cash_balance": 0, "asset_quantity": 0,
|
||||
"strategy_name": ""},
|
||||
], "total": 1})
|
||||
|
||||
adapter = PortfolioHubReadAdapter("https://pf.test", client=_client(handler))
|
||||
items = adapter.get_portfolios("d-7")
|
||||
assert items[0].strategy_name is None
|
||||
|
||||
|
||||
def test_get_portfolios_markets_non_list_becomes_empty():
|
||||
"""REQ-116 AC-2: markets not a list (None, str, int) → []."""
|
||||
for bad_val in [None, "KRX", 42]:
|
||||
def handler(req: httpx.Request, _v=bad_val) -> httpx.Response:
|
||||
return httpx.Response(200, json={"portfolios": [
|
||||
{"portfolio_id": "pf-1", "symbol": "005930",
|
||||
"cash_balance": 0, "asset_quantity": 0,
|
||||
"markets": _v},
|
||||
], "total": 1})
|
||||
adapter = PortfolioHubReadAdapter("https://pf.test", client=_client(handler))
|
||||
items = adapter.get_portfolios("d-7")
|
||||
assert items[0].markets == [], f"expected [] for markets={bad_val!r}"
|
||||
|
||||
|
||||
def test_get_portfolios_strategy_fields_absent_safe_default():
|
||||
"""REQ-116 AC-2: hub response has no strategy_name/markets keys → safe defaults."""
|
||||
def handler(req: httpx.Request) -> httpx.Response:
|
||||
return httpx.Response(200, json={"portfolios": [
|
||||
{"portfolio_id": "pf-1", "symbol": "005930",
|
||||
"cash_balance": 0, "asset_quantity": 0},
|
||||
], "total": 1})
|
||||
|
||||
adapter = PortfolioHubReadAdapter("https://pf.test", client=_client(handler))
|
||||
items = adapter.get_portfolios("d-7")
|
||||
assert items[0].strategy_name is None
|
||||
assert items[0].markets == []
|
||||
assert items[0].strategy_params == {}
|
||||
|
||||
Reference in New Issue
Block a user