feat(slot): BAT-027 M2/M3 — 상태머신 inactive 재정의 + 인스턴스 스코프 advisory lock (dcy-020/REQ-057)
안 B 점진의 2/3단계. runtime 무변경(idle/closed 별칭 흡수)으로 cascade 통제. - state.py: SlotState.INACTIVE 추가 + idle/closed 별칭 normalize_state(); 전이 집합 inactive 기준 재정의(inactive↔pending_buy↔holding↔pending_sell), closed→idle 제거; is_allowed_transition 양쪽 정규화; slot_advisory_lock_id(instance_key) 헬퍼 - service.py: SlotService(instance_key) + advisory lock 인스턴스 스코프(self._lock_id) - __init__: normalize_state/slot_advisory_lock_id export; daemon: instance_key 전달 - 테스트 갱신: test_state(전이/멤버7/락/size12), test_service(closed→idle 제거→reallocation), test_slot_entry_count, test_postgresql_repository(closed→idle 거부+reallocation) 검증: 단위 375 passed / 35 skipped, PG-gated(trex_core_test) 8 passed (마이그레이션 009 드리프트 + lifecycle/reallocation), ruff clean. REQ-056/057 은 토대만 충족 → Approved 유지(entry_count/trading_date drop·literal inactive 전면 전환은 BAT-δ; confirm-req-implemented 는 dcy-020 후속 배치 완료 후). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -3,6 +3,8 @@ from __future__ import annotations
|
||||
|
||||
from datetime import date
|
||||
|
||||
import pytest
|
||||
|
||||
from trex_core.adapters.persistence.in_memory_intent_store import InMemoryIntentStore # noqa: F401
|
||||
from trex_core.adapters.persistence.postgresql import (
|
||||
PostgreSQLIntentStore,
|
||||
@@ -10,6 +12,7 @@ from trex_core.adapters.persistence.postgresql import (
|
||||
)
|
||||
from trex_core.application.intents import BuyIntent, IntentService, SellIntent
|
||||
from trex_core.domain.slot import SlotService, SlotState
|
||||
from trex_core.domain.slot.errors import SlotTransitionError
|
||||
|
||||
DAY = date(2026, 6, 3)
|
||||
|
||||
@@ -48,7 +51,9 @@ def test_buy_intent_idempotent_on_pg(pg_clean):
|
||||
assert len(pending) == 1 # no double transition
|
||||
|
||||
|
||||
def test_full_lifecycle_recovery_bumps_entry_count(pg_clean):
|
||||
def test_full_lifecycle_then_closed_reallocation_bumps_entry_count(pg_clean):
|
||||
# dcy-020: closed→idle recovery removed. Reuse a closed slot via
|
||||
# closed→pending_buy reallocation (dcy-019); entry_count is cumulative.
|
||||
repo, svc, intents = _wire(pg_clean)
|
||||
svc.preallocate_daily_slots(DAY, 1)
|
||||
d = intents.submit_buy(BuyIntent(intent_id="pg-life", trading_date=DAY, symbol="005930", portfolio_id="PF-1"))
|
||||
@@ -56,6 +61,11 @@ def test_full_lifecycle_recovery_bumps_entry_count(pg_clean):
|
||||
svc.transit_slot(sid, SlotState.PENDING_BUY, SlotState.HOLDING, reason="filled", actor="t")
|
||||
intents.submit_sell(SellIntent(intent_id="pg-sell", slot_id=sid))
|
||||
svc.transit_slot(sid, SlotState.PENDING_SELL, SlotState.CLOSED, reason="filled", actor="t")
|
||||
assert repo.get_symbol_entry_count(DAY, "005930") == 0
|
||||
svc.transit_slot(sid, SlotState.CLOSED, SlotState.IDLE, reason="recovery", actor="t")
|
||||
assert repo.get_symbol_entry_count(DAY, "005930") == 1
|
||||
assert repo.fetch_slot(sid).entry_count == 1
|
||||
# closed→idle is now rejected
|
||||
with pytest.raises(SlotTransitionError):
|
||||
svc.transit_slot(sid, SlotState.CLOSED, SlotState.IDLE, reason="recovery", actor="t")
|
||||
# reuse via reallocation
|
||||
realloc = svc.transit_slot(sid, SlotState.CLOSED, SlotState.PENDING_BUY,
|
||||
reason="reallocate", actor="t", symbol="000660")
|
||||
assert realloc.entry_count == 2 and realloc.symbol == "000660"
|
||||
|
||||
Reference in New Issue
Block a user