"""Snapshot RPC handler tests (REQ-021 AC-2, BAT-007 stub-shape).""" from __future__ import annotations from datetime import date from trex_core.adapters.persistence.in_memory_intent_store import InMemoryIntentStore from trex_core.application.intents import BuyIntent, IntentService from trex_core.application.messaging import RpcRequest from trex_core.application.messaging_handlers import make_snapshot_rpc_handler from trex_core.domain.slot import SlotService from trex_core.domain.slot.in_memory_repository import InMemorySlotRepository DAY = date(2026, 6, 5) def _wire(): repo = InMemorySlotRepository() svc = SlotService(repo) intents = IntentService(svc, repo, InMemoryIntentStore()) svc.preallocate_daily_slots(DAY, 3) return repo, svc, intents def _req(view="all", strategy_id=None): return RpcRequest( payload={ "view": view, "strategy_id": strategy_id, "trading_date": DAY.isoformat(), }, correlation_id="snap-cid", ) def test_snapshot_returns_all_slots_and_capacity(): repo, svc, intents = _wire() intents.submit_buy(BuyIntent( intent_id="b1", trading_date=DAY, symbol="005930", qty=10, strategy_id="A", portfolio_id="PF-1", )) handler = make_snapshot_rpc_handler(repo, svc) resp = handler(_req()) assert resp.payload["trading_date"] == DAY.isoformat() assert resp.payload["view"] == "all" assert len(resp.payload["slots"]) == 3 # all 3 slots returned cap = resp.payload["capacity"] assert cap["pending_count"] == 1 and cap["idle_count"] == 2 def test_snapshot_open_view_filters_to_active_slots(): repo, svc, intents = _wire() intents.submit_buy(BuyIntent( intent_id="b1", trading_date=DAY, symbol="005930", qty=10, strategy_id="A", portfolio_id="PF-1", )) handler = make_snapshot_rpc_handler(repo, svc) resp = handler(_req(view="open")) statuses = {s["status"] for s in resp.payload["slots"]} assert "idle" not in statuses assert statuses == {"pending_buy"} def test_snapshot_strategy_id_filter(): repo, svc, intents = _wire() intents.submit_buy(BuyIntent( intent_id="b1", trading_date=DAY, symbol="005930", qty=10, strategy_id="A", portfolio_id="PF-1", )) intents.submit_buy(BuyIntent( intent_id="b2", trading_date=DAY, symbol="000660", qty=5, strategy_id="B", portfolio_id="PF-2", )) handler = make_snapshot_rpc_handler(repo, svc) resp = handler(_req(view="open", strategy_id="A")) assert len(resp.payload["slots"]) == 1 assert resp.payload["slots"][0]["strategy_id"] == "A" assert resp.payload["slots"][0]["symbol"] == "005930" # --- dcy-016 / REQ-021/034: operator query watchdog suppression ------------- class _RecWatchdog: def __init__(self): self.updated = [] def update(self, slot_id): self.updated.append(str(slot_id)) def _holding_slot(repo, svc): from trex_core.domain.slot import SlotState slot = next(s for s in repo.list_slots(DAY) if s.status is SlotState.IDLE) svc.transit_slot(slot.slot_id, SlotState.IDLE, SlotState.PENDING_BUY, reason="t", actor="t", symbol="005930", portfolio_id="PF-1", qty=10) svc.transit_slot(slot.slot_id, SlotState.PENDING_BUY, SlotState.HOLDING, reason="t", actor="t", avg_price=1000) return str(slot.slot_id) def test_strategy_poll_touches_watchdog_by_default(): repo, svc, intents = _wire() sid = _holding_slot(repo, svc) wd = _RecWatchdog() handler = make_snapshot_rpc_handler(repo, svc, watchdog_store=wd) handler(_req()) # no actor flag => legacy liveness touch (REQ-034) assert sid in wd.updated def test_operator_query_suppresses_watchdog_touch(): repo, svc, intents = _wire() _holding_slot(repo, svc) wd = _RecWatchdog() handler = make_snapshot_rpc_handler(repo, svc, watchdog_store=wd) req = RpcRequest( payload={"view": "all", "trading_date": DAY.isoformat(), "actor": "operator", "touch_watchdog": False}, correlation_id="op", ) handler(req) assert wd.updated == [] # dcy-016 R-4: operator query is not a liveness signal