perf(slots): BAT-040 — slots RPC DB-level status 필터 (REQ-002/006/073, issue #25)
REQ-002/006 change-req(CHG-20260612-01)→구현→confirm-req-implemented, REQ-073 change-req(CHG-20260612-02, 관찰 계약 불변 Implemented 유지) 전체 delivery. - repository.py/in_memory_repository.py: SlotRepository.list_slots(instance_key, status_filter=None) — status 값 집합 필터, None=전체(기존 호출자 불변). - postgresql.py: list_slots DB-level WHERE status = ANY(%s) (full scan + Python 필터 제거). 스키마/마이그레이션 변경 없음. - messaging_handlers.py: _FILTER_STATUS 매핑으로 filter→status 집합 위임, watchdog 갱신용 holding 조회를 all/holding fetch 결과에서 재사용(2nd 커넥션 생략). - 관찰 가능한 RPC 계약(REQ-073 AC-1~6) 불변 — 동작 보존 최적화. - tests: in-memory status_filter 5종 + PG 게이트 1종. 439 passed (+5), ruff clean. REQ-002·006 Approved→Implemented, REQ-073 Implemented 유지, BAT-040 release-candidate. issue #25 close. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -71,6 +71,19 @@ def test_buy_intent_idempotent_on_pg(pg_clean):
|
||||
assert len(pending) == 1 # no double transition
|
||||
|
||||
|
||||
def test_list_slots_status_filter_pg(pg_clean):
|
||||
# REQ-006 CHG-20260612-01 (issue #25): DB-level status filter (status = ANY).
|
||||
repo, svc, intents = _wire(pg_clean)
|
||||
repo.reconcile_universe(INST, _SYMS) # 3 inactive
|
||||
intents.submit_buy(BuyIntent(intent_id="pg-f1", qty=10, portfolio_id="PF-1")) # PF-1 -> pending_buy
|
||||
|
||||
assert len(repo.list_slots(INST)) == 3 # None -> all
|
||||
assert len(repo.list_slots(INST, status_filter={"inactive"})) == 2
|
||||
pending = repo.list_slots(INST, status_filter={"pending_buy", "pending_sell"})
|
||||
assert len(pending) == 1 and pending[0].status is SlotState.PENDING_BUY
|
||||
assert repo.list_slots(INST, status_filter={"error"}) == []
|
||||
|
||||
|
||||
def test_buy_by_slot_id_contract_pg(pg_clean):
|
||||
# dcy-020/REQ-061: slot_id is the canonical buy key against real PG.
|
||||
repo, svc, intents = _wire(pg_clean)
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
"""list_slots status_filter (REQ-002/006 CHG-20260612-01, issue #25).
|
||||
|
||||
Pushes status filtering to the repository boundary so the slots RPC handler no
|
||||
longer fetches every row and filters in Python. None preserves prior behaviour
|
||||
(all slots), so existing unfiltered callers are unaffected.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
from trex_core.domain.slot import SlotService, SlotState
|
||||
from trex_core.domain.slot.in_memory_repository import InMemorySlotRepository
|
||||
|
||||
INST = "inst-1"
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def repo():
|
||||
return InMemorySlotRepository()
|
||||
|
||||
|
||||
def _seed(repo):
|
||||
"""3 inactive + 1 holding + 1 pending_buy for INST."""
|
||||
svc = SlotService(repo, instance_key=INST)
|
||||
repo.reconcile_universe(INST, {f"pf-{i}": "SYM" for i in range(5)})
|
||||
# pf-0 -> holding
|
||||
s0 = repo.find_slot_by_portfolio(INST, "pf-0")
|
||||
svc.transit_slot(s0.slot_id, s0.status, SlotState.PENDING_BUY,
|
||||
reason="r", actor="a", symbol="005930")
|
||||
svc.transit_slot(s0.slot_id, SlotState.PENDING_BUY, SlotState.HOLDING,
|
||||
reason="r", actor="a", avg_price=70_000)
|
||||
# pf-1 -> pending_buy
|
||||
s1 = repo.find_slot_by_portfolio(INST, "pf-1")
|
||||
svc.transit_slot(s1.slot_id, s1.status, SlotState.PENDING_BUY,
|
||||
reason="r", actor="a", symbol="005930")
|
||||
# pf-2,3,4 remain inactive
|
||||
return svc
|
||||
|
||||
|
||||
def test_none_returns_all(repo):
|
||||
_seed(repo)
|
||||
assert len(repo.list_slots(INST)) == 5
|
||||
assert len(repo.list_slots(INST, status_filter=None)) == 5
|
||||
|
||||
|
||||
def test_single_status_filter(repo):
|
||||
_seed(repo)
|
||||
holding = repo.list_slots(INST, status_filter={"holding"})
|
||||
assert len(holding) == 1 and holding[0].status is SlotState.HOLDING
|
||||
|
||||
inactive = repo.list_slots(INST, status_filter={"inactive"})
|
||||
assert len(inactive) == 3
|
||||
assert all(s.status is SlotState.INACTIVE for s in inactive)
|
||||
|
||||
|
||||
def test_pending_group_filter(repo):
|
||||
_seed(repo)
|
||||
pending = repo.list_slots(INST, status_filter={"pending_buy", "pending_sell"})
|
||||
assert len(pending) == 1 and pending[0].status is SlotState.PENDING_BUY
|
||||
|
||||
|
||||
def test_empty_when_no_match(repo):
|
||||
_seed(repo)
|
||||
assert repo.list_slots(INST, status_filter={"error"}) == []
|
||||
|
||||
|
||||
def test_instance_scoped(repo):
|
||||
_seed(repo)
|
||||
# other instance has no slots
|
||||
assert repo.list_slots("other", status_filter={"holding"}) == []
|
||||
Reference in New Issue
Block a user