Fence unmanaged lifecycle by snapshot time
Co-authored-by: genichin <4282227+genichin@users.noreply.github.com>
This commit is contained in:
co-authored by
genichin
parent
84d81658a1
commit
8b1de8e26a
@@ -161,13 +161,83 @@ class TestCRUDRoundTrip:
|
||||
audit = store.list_audit(instance_key="inst-A", symbol="008110")
|
||||
assert [row["event_type"] for row in audit] == ["detected"]
|
||||
|
||||
def test_concurrent_observe_and_resolve_leave_consistent_state_and_audit(self, pg_clean):
|
||||
def test_older_absent_snapshot_applied_after_newer_observation_is_ignored(self, pg_unmanaged_store):
|
||||
t1 = datetime(2026, 9, 7, 0, 0, tzinfo=UTC)
|
||||
t2 = t1 + timedelta(minutes=1)
|
||||
t3 = t2 + timedelta(minutes=1)
|
||||
|
||||
pg_unmanaged_store.observe(
|
||||
instance_key="inst-A",
|
||||
symbol="008110",
|
||||
quantity=3,
|
||||
observed_at=t1,
|
||||
)
|
||||
observed = pg_unmanaged_store.observe(
|
||||
instance_key="inst-A",
|
||||
symbol="008110",
|
||||
quantity=5,
|
||||
observed_at=t3,
|
||||
)
|
||||
resolved = pg_unmanaged_store.resolve_absent(
|
||||
instance_key="inst-A",
|
||||
observed_symbols=set(),
|
||||
observed_at=t2,
|
||||
)
|
||||
|
||||
assert observed["transition"] == "quantity_changed"
|
||||
assert resolved == []
|
||||
state = pg_unmanaged_store.get_state(instance_key="inst-A", symbol="008110")
|
||||
assert state is not None
|
||||
assert state["lifecycle_state"] == "active"
|
||||
assert state["quantity"] == 5
|
||||
assert [row["event_type"] for row in pg_unmanaged_store.list_audit(
|
||||
instance_key="inst-A",
|
||||
symbol="008110",
|
||||
)] == ["detected", "quantity_changed"]
|
||||
|
||||
def test_older_observation_applied_after_newer_resolution_is_ignored(self, pg_unmanaged_store):
|
||||
t1 = datetime(2026, 9, 7, 0, 0, tzinfo=UTC)
|
||||
t2 = t1 + timedelta(minutes=1)
|
||||
t3 = t2 + timedelta(minutes=1)
|
||||
|
||||
pg_unmanaged_store.observe(
|
||||
instance_key="inst-A",
|
||||
symbol="008110",
|
||||
quantity=3,
|
||||
observed_at=t1,
|
||||
)
|
||||
pg_unmanaged_store.resolve_absent(
|
||||
instance_key="inst-A",
|
||||
observed_symbols=set(),
|
||||
observed_at=t3,
|
||||
)
|
||||
stale = pg_unmanaged_store.observe(
|
||||
instance_key="inst-A",
|
||||
symbol="008110",
|
||||
quantity=5,
|
||||
observed_at=t2,
|
||||
)
|
||||
|
||||
assert stale["transition"] == "stale"
|
||||
state = pg_unmanaged_store.get_state(instance_key="inst-A", symbol="008110")
|
||||
assert state is not None
|
||||
assert state["lifecycle_state"] == "resolved"
|
||||
assert state["quantity"] == 3
|
||||
assert [row["event_type"] for row in pg_unmanaged_store.list_audit(
|
||||
instance_key="inst-A",
|
||||
symbol="008110",
|
||||
)] == ["detected", "resolved"]
|
||||
|
||||
def test_concurrent_observe_and_resolve_respects_newest_snapshot_timestamp(self, pg_clean):
|
||||
from trex_core.adapters.portfolio.unmanaged_position_store import (
|
||||
PostgreSQLUnmanagedPositionStore,
|
||||
)
|
||||
|
||||
store = PostgreSQLUnmanagedPositionStore(pg_clean)
|
||||
store.observe(instance_key="inst-A", symbol="008110", quantity=3)
|
||||
t1 = datetime(2026, 9, 7, 0, 0, tzinfo=UTC)
|
||||
t2 = t1 + timedelta(minutes=1)
|
||||
t3 = t2 + timedelta(minutes=1)
|
||||
store.observe(instance_key="inst-A", symbol="008110", quantity=3, observed_at=t1)
|
||||
|
||||
with ThreadPoolExecutor(max_workers=2) as pool:
|
||||
observe_future = pool.submit(
|
||||
@@ -175,32 +245,24 @@ class TestCRUDRoundTrip:
|
||||
instance_key="inst-A",
|
||||
symbol="008110",
|
||||
quantity=5,
|
||||
observed_at=t3,
|
||||
)
|
||||
resolve_future = pool.submit(
|
||||
PostgreSQLUnmanagedPositionStore(pg_clean).resolve_absent,
|
||||
instance_key="inst-A",
|
||||
observed_symbols=set(),
|
||||
observed_at=t2,
|
||||
)
|
||||
observe_result = observe_future.result()
|
||||
resolve_result = resolve_future.result()
|
||||
|
||||
state = store.get_state(instance_key="inst-A", symbol="008110")
|
||||
assert state is not None
|
||||
audit = store.list_audit(instance_key="inst-A", symbol="008110")
|
||||
event_types = [row["event_type"] for row in audit]
|
||||
assert event_types[0] == "detected"
|
||||
assert event_types in (
|
||||
["detected", "quantity_changed", "resolved"],
|
||||
["detected", "resolved", "detected"],
|
||||
)
|
||||
if state["lifecycle_state"] == "resolved":
|
||||
assert observe_result["transition"] == "quantity_changed"
|
||||
assert len(resolve_result) == 1
|
||||
assert state["quantity"] == 5
|
||||
assert event_types[-1] == "resolved"
|
||||
else:
|
||||
assert state["lifecycle_state"] == "active"
|
||||
assert observe_result["transition"] == "detected"
|
||||
assert len(resolve_result) == 1
|
||||
assert state["quantity"] == 5
|
||||
assert event_types[-1] == "detected"
|
||||
assert observe_result["transition"] == "quantity_changed"
|
||||
assert resolve_result == []
|
||||
assert state["lifecycle_state"] == "active"
|
||||
assert state["quantity"] == 5
|
||||
assert [row["event_type"] for row in store.list_audit(
|
||||
instance_key="inst-A",
|
||||
symbol="008110",
|
||||
)] == ["detected", "quantity_changed"]
|
||||
|
||||
Reference in New Issue
Block a user