"""Snapshot RPC handler watchdog integration tests (REQ-034 AC-1 / BAT-010).""" from __future__ import annotations from datetime import date import pytest from trex_core.application.messaging import RpcRequest from trex_core.application.messaging_handlers import make_snapshot_rpc_handler from trex_core.application.watchdog import WatchdogStore from trex_core.domain.slot import SlotService, SlotState from trex_core.domain.slot.in_memory_repository import InMemorySlotRepository @pytest.fixture def trading_day(): return date(2026, 6, 7) @pytest.fixture def repo(): return InMemorySlotRepository() @pytest.fixture def service(repo): return SlotService(repo) def _make_req(trading_day): return RpcRequest( payload={"trading_date": trading_day.isoformat(), "view": "all"}, correlation_id="test-corr", ) def _buy_slot(service, repo, trading_day, symbol="005930"): """Seed an asset slot + buy it into HOLDING state.""" pid = f"pf-{symbol}" repo.reconcile_universe("default", {pid}) slot = repo.find_slot_by_portfolio("default", pid) pending = service.transit_slot( slot.slot_id, SlotState.INACTIVE, SlotState.PENDING_BUY, reason="buy", actor="test", symbol=symbol, ) holding = service.transit_slot( pending.slot_id, SlotState.PENDING_BUY, SlotState.HOLDING, reason="filled", actor="test", avg_price=100.0, qty=10.0, ) return holding class TestSnapshotRpcWatchdog: def test_holding_slot_watchdog_updated_on_snapshot_rpc(self, repo, service, trading_day): """AC-1: snapshot RPC updates watchdog for each holding slot in response.""" holding = _buy_slot(service, repo, trading_day) watchdog = WatchdogStore() handler = make_snapshot_rpc_handler(repo, service, watchdog_store=watchdog) handler(_make_req(trading_day)) assert str(holding.slot_id) in watchdog._store def test_idle_slot_watchdog_not_updated(self, repo, service, trading_day): """Only HOLDING slots update the watchdog — inactive slots are ignored.""" repo.reconcile_universe("default", {"pf-X"}) inactive = repo.find_slot_by_portfolio("default", "pf-X") watchdog = WatchdogStore() handler = make_snapshot_rpc_handler(repo, service, watchdog_store=watchdog) handler(_make_req(trading_day)) assert str(inactive.slot_id) not in watchdog._store def test_no_watchdog_store_does_not_raise(self, repo, service, trading_day): """Without watchdog_store= the handler works as before (backward compat).""" _buy_slot(service, repo, trading_day) handler = make_snapshot_rpc_handler(repo, service) resp = handler(_make_req(trading_day)) assert resp is not None def test_multiple_holding_slots_all_updated(self, repo, service, trading_day): """All holding slots in the response have their watchdog updated.""" slots = [] for sym in ("A", "B", "C"): repo.reconcile_universe("default", {f"pf-{sym}"}) slot = repo.find_slot_by_portfolio("default", f"pf-{sym}") p = service.transit_slot( slot.slot_id, SlotState.INACTIVE, SlotState.PENDING_BUY, reason="buy", actor="test", symbol=sym, ) h = service.transit_slot( p.slot_id, SlotState.PENDING_BUY, SlotState.HOLDING, reason="filled", actor="test", avg_price=100.0, qty=1.0, ) slots.append(h) watchdog = WatchdogStore() handler = make_snapshot_rpc_handler(repo, service, watchdog_store=watchdog) handler(_make_req(trading_day)) for s in slots: assert str(s.slot_id) in watchdog._store, f"{s.slot_id} not in watchdog"