Resolve main merge and add unmanaged store race tests

Co-authored-by: genichin <4282227+genichin@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot]
2026-09-13 02:58:18 +00:00
committed by GitHub
co-authored by genichin
parent c73e382b70
commit 84d81658a1
4 changed files with 74 additions and 5 deletions
@@ -1,6 +1,7 @@
"""PostgreSQLUnmanagedPositionStore integration tests."""
from __future__ import annotations
from concurrent.futures import ThreadPoolExecutor
from datetime import UTC, datetime, timedelta
import pytest
@@ -136,3 +137,70 @@ class TestCRUDRoundTrip:
assert reopened["state"]["last_seen_at"] == t3
assert reopened["state"]["operator_action"] is None
assert reopened["state"]["evidence_ref"] is None
def test_concurrent_first_observe_keeps_single_detected_audit(self, pg_clean):
from trex_core.adapters.portfolio.unmanaged_position_store import (
PostgreSQLUnmanagedPositionStore,
)
def _observe():
return PostgreSQLUnmanagedPositionStore(pg_clean).observe(
instance_key="inst-A",
symbol="008110",
quantity=3,
)
with ThreadPoolExecutor(max_workers=2) as pool:
results = list(pool.map(lambda _n: _observe(), range(2)))
assert sorted(result["transition"] for result in results) == ["detected", "unchanged"]
store = PostgreSQLUnmanagedPositionStore(pg_clean)
state = store.get_state(instance_key="inst-A", symbol="008110")
assert state is not None
assert state["lifecycle_state"] == "active"
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):
from trex_core.adapters.portfolio.unmanaged_position_store import (
PostgreSQLUnmanagedPositionStore,
)
store = PostgreSQLUnmanagedPositionStore(pg_clean)
store.observe(instance_key="inst-A", symbol="008110", quantity=3)
with ThreadPoolExecutor(max_workers=2) as pool:
observe_future = pool.submit(
PostgreSQLUnmanagedPositionStore(pg_clean).observe,
instance_key="inst-A",
symbol="008110",
quantity=5,
)
resolve_future = pool.submit(
PostgreSQLUnmanagedPositionStore(pg_clean).resolve_absent,
instance_key="inst-A",
observed_symbols=set(),
)
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"
@@ -98,6 +98,7 @@ def test_in_memory_store_tracks_lifecycle_and_operator_audit():
"operator_action",
"resolved",
]
assert all("recorded_at" in row for row in audit)
def test_in_memory_store_isolates_same_symbol_by_instance():
@@ -258,7 +258,7 @@ class PostgreSQLUnmanagedPositionStore:
"actor": row[4],
"evidence_ref": row[5],
"note": row[6],
"created_at": row[7],
"recorded_at": row[7],
}
for row in cur.fetchall()
]
+4 -4
View File
@@ -660,7 +660,7 @@ class InMemoryUnmanagedPositionStore:
"operator_note": "",
"operator_actor": None,
"operator_updated_at": None,
"created_at": now,
"recorded_at": now,
**preserved,
"updated_at": now,
}
@@ -695,7 +695,7 @@ class InMemoryUnmanagedPositionStore:
if transition == "quantity_changed" and previous_quantity is not None
else ""
),
"created_at": now,
"recorded_at": now,
}
)
return {
@@ -737,7 +737,7 @@ class InMemoryUnmanagedPositionStore:
"actor": actor,
"evidence_ref": state.get("evidence_ref"),
"note": "",
"created_at": now,
"recorded_at": now,
}
)
return resolved
@@ -798,7 +798,7 @@ class InMemoryUnmanagedPositionStore:
"actor": actor,
"evidence_ref": evidence_ref,
"note": note,
"created_at": now,
"recorded_at": now,
}
)
return dict(state)