"""Crash/restart reconcile convergence tests for PINK → DITAv2.""" from __future__ import annotations from datetime import datetime, timezone import unittest from prod.clean_arch.dita_v2 import ( ExecutionKernel, InMemoryControlPlane, InMemoryZincPlane, KernelCommandType, KernelIntent, MockVenueAdapter, MockVenueScenario, TradeSide, TradeSlot, TradeStage, ) class TestRestartReconcile(unittest.TestCase): """Verify exchange-led state convergence after simulated crash/restart.""" def setUp(self): self.control = InMemoryControlPlane() self.venue = MockVenueAdapter() # deterministic mock self.kernel = ExecutionKernel( max_slots=2, control_plane=self.control, venue=self.venue, zinc_plane=InMemoryZincPlane(), ) def _enter_position(self) -> None: intent = KernelIntent( timestamp=datetime.now(timezone.utc), intent_id="entry-001", trade_id="trade-001", slot_id=0, asset="BTCUSDT", side=TradeSide.SHORT, action=KernelCommandType.ENTER, reference_price=65000.0, target_size=0.01, leverage=2.0, reason="restart_test_entry", ) self.kernel.process_intent(intent) def test_entry_opens_slot(self): self._enter_position() slot = self.kernel.slot(0) self.assertTrue(slot.is_open(), f"Expected open slot after entry, got {slot.fsm_state}") def test_reconcile_with_empty_does_not_crash(self): self._enter_position() # Reconcile with empty list — no-op outcome = self.kernel.reconcile_from_slots([]) self.assertIsNotNone(outcome, "Reconcile should return an outcome") def test_capital_seed_after_reconcile(self): self._enter_position() capital_before = self.kernel.account.snapshot.capital self.assertGreater(capital_before, 0) self.kernel.reconcile_from_slots([]) capital_after = self.kernel.account.snapshot.capital self.assertEqual(capital_after, capital_before, "Capital should not change during reconcile") if __name__ == "__main__": unittest.main()