"""dcy-020 / REQ-058 (BAT-028): universe reconcile lifecycle (in-memory).""" from __future__ import annotations from datetime import date import pytest from trex_core.domain.slot import SlotService, SlotState from trex_core.domain.slot.in_memory_repository import InMemorySlotRepository DAY = date(2026, 6, 10) INST = "inst-1" @pytest.fixture def repo(): return InMemorySlotRepository() def test_creates_asset_slots_idempotently(repo): r1 = repo.reconcile_universe(INST, DAY, {"pf-a", "pf-b"}) assert r1["created"] == 2 # idempotent — second pass creates nothing r2 = repo.reconcile_universe(INST, DAY, {"pf-a", "pf-b"}) assert r2["created"] == 0 assert repo.find_slot_by_portfolio(INST, "pf-a").status is SlotState.IDLE def test_soft_retire_inactive_asset_that_left_universe(repo): repo.reconcile_universe(INST, DAY, {"pf-a", "pf-b"}) r = repo.reconcile_universe(INST, DAY, {"pf-a"}) # pf-b left assert r["retired"] == 1 slot_b = repo.find_slot_by_portfolio(INST, "pf-b") assert slot_b.retired_at is not None # soft-retired, row preserved def test_tombstone_holding_asset_that_left_universe(repo): svc = SlotService(repo, instance_key=INST) repo.reconcile_universe(INST, DAY, {"pf-a"}) slot = repo.find_slot_by_portfolio(INST, "pf-a") svc.transit_slot(slot.slot_id, slot.status, SlotState.PENDING_BUY, reason="r", actor="a", symbol="005930") svc.transit_slot(slot.slot_id, SlotState.PENDING_BUY, SlotState.HOLDING, reason="r", actor="a", avg_price=70_000) r = repo.reconcile_universe(INST, DAY, set()) # pf-a left while holding assert r["tombstoned"] == 1 and r["retired"] == 0 held = repo.find_slot_by_portfolio(INST, "pf-a") assert held.status is SlotState.HOLDING and held.retired_at is None # protected def test_allow_retire_false_skips_retire(repo): repo.reconcile_universe(INST, DAY, {"pf-a", "pf-b"}) r = repo.reconcile_universe(INST, DAY, {"pf-a"}, allow_retire=False) assert r["retired"] == 0 assert repo.find_slot_by_portfolio(INST, "pf-b").retired_at is None def test_count_active(repo): svc = SlotService(repo, instance_key=INST) repo.reconcile_universe(INST, DAY, {"pf-a", "pf-b"}) assert repo.count_active(INST) == 0 slot = repo.find_slot_by_portfolio(INST, "pf-a") svc.transit_slot(slot.slot_id, slot.status, SlotState.PENDING_BUY, reason="r", actor="a", symbol="005930") assert repo.count_active(INST) == 1 # pending_buy counts as active def test_instance_isolation(repo): repo.reconcile_universe("inst-1", DAY, {"pf-a"}) repo.reconcile_universe("inst-2", DAY, {"pf-a"}) # same portfolio_id, different instances -> two distinct slots assert repo.find_slot_by_portfolio("inst-1", "pf-a").slot_id != \ repo.find_slot_by_portfolio("inst-2", "pf-a").slot_id assert repo.count_active("inst-1") == 0