feat: add kill-switch control flow
This commit is contained in:
@@ -0,0 +1,79 @@
|
||||
"""Kill-switch day-scoped exclusion store tests (REQ-044 / BAT-012).
|
||||
|
||||
In-memory tests run always; PostgreSQL tests are gated on TREX_POSTGRESQL_TEST_DSN.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import date
|
||||
|
||||
import pytest
|
||||
|
||||
from trex_core.application.portfolio import GLOBAL_KILLSWITCH, InMemoryKillSwitchStore
|
||||
|
||||
_D = date(2026, 6, 7)
|
||||
_D_NEXT = date(2026, 6, 8)
|
||||
|
||||
|
||||
class TestInMemoryKillSwitchStore:
|
||||
def test_exclude_then_list(self):
|
||||
s = InMemoryKillSwitchStore()
|
||||
s.exclude("pf-A", _D)
|
||||
assert "pf-A" in s.list_excluded(_D)
|
||||
|
||||
def test_day_scoped_auto_clear(self):
|
||||
"""AC-4: next trading day does not include today's exclusion."""
|
||||
s = InMemoryKillSwitchStore()
|
||||
s.exclude("pf-A", _D)
|
||||
assert "pf-A" not in s.list_excluded(_D_NEXT)
|
||||
|
||||
def test_idempotent_exclude(self):
|
||||
s = InMemoryKillSwitchStore()
|
||||
s.exclude("pf-A", _D)
|
||||
s.exclude("pf-A", _D)
|
||||
assert s.list_excluded(_D) == {"pf-A"}
|
||||
|
||||
def test_clear(self):
|
||||
s = InMemoryKillSwitchStore()
|
||||
s.exclude("pf-A", _D)
|
||||
s.clear("pf-A", _D)
|
||||
assert "pf-A" not in s.list_excluded(_D)
|
||||
|
||||
def test_global_sentinel(self):
|
||||
"""AC-7: global exclusion stored under the '*' sentinel."""
|
||||
s = InMemoryKillSwitchStore()
|
||||
s.exclude(GLOBAL_KILLSWITCH, _D)
|
||||
assert GLOBAL_KILLSWITCH in s.list_excluded(_D)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def pg_killswitch_store(pg_clean):
|
||||
from trex_core.adapters.portfolio.killswitch_store import PostgreSQLKillSwitchStore
|
||||
return PostgreSQLKillSwitchStore(pg_clean)
|
||||
|
||||
|
||||
class TestPostgreSQLKillSwitchStore:
|
||||
def test_schema_and_round_trip(self, pg_killswitch_store):
|
||||
"""AC-1/3: table exists; exclude→list_excluded round-trips."""
|
||||
pg_killswitch_store.exclude("pf-A", _D, actor="op")
|
||||
assert pg_killswitch_store.list_excluded(_D) == {"pf-A"}
|
||||
|
||||
def test_day_scoped(self, pg_killswitch_store):
|
||||
"""AC-4: list_excluded(next day) excludes today's row."""
|
||||
pg_killswitch_store.exclude("pf-A", _D)
|
||||
assert pg_killswitch_store.list_excluded(_D_NEXT) == set()
|
||||
|
||||
def test_idempotent(self, pg_killswitch_store):
|
||||
"""AC-5: ON CONFLICT DO NOTHING — duplicate exclude is a no-op."""
|
||||
pg_killswitch_store.exclude("pf-A", _D)
|
||||
pg_killswitch_store.exclude("pf-A", _D)
|
||||
assert pg_killswitch_store.list_excluded(_D) == {"pf-A"}
|
||||
|
||||
def test_clear(self, pg_killswitch_store):
|
||||
"""AC-6: clear removes the row."""
|
||||
pg_killswitch_store.exclude("pf-A", _D)
|
||||
pg_killswitch_store.clear("pf-A", _D)
|
||||
assert pg_killswitch_store.list_excluded(_D) == set()
|
||||
|
||||
def test_global_sentinel(self, pg_killswitch_store):
|
||||
pg_killswitch_store.exclude(GLOBAL_KILLSWITCH, _D)
|
||||
assert GLOBAL_KILLSWITCH in pg_killswitch_store.list_excluded(_D)
|
||||
Reference in New Issue
Block a user