test: make context-failure regressions deterministic and simplify alarm log handling
Co-authored-by: genichin <4282227+genichin@users.noreply.github.com>
This commit is contained in:
co-authored by
genichin
parent
1ca9e60c98
commit
75406af970
@@ -101,22 +101,29 @@ class DuplicateInFlightIntentStore:
|
|||||||
raise AssertionError("finalize must not be called for duplicate_in_flight")
|
raise AssertionError("finalize must not be called for duplicate_in_flight")
|
||||||
|
|
||||||
|
|
||||||
class ContextFailingSlotRepository(InMemorySlotRepository):
|
class PostFinalizeContextFailingSlotRepository(InMemorySlotRepository):
|
||||||
"""Raise only on the N+1th fetch_slot to simulate post-finalize lookup failure."""
|
"""Raise fetch_slot only after explicitly armed by finalize."""
|
||||||
|
|
||||||
def __init__(self, *, fail_on_fetch_call_no=None):
|
def __init__(self):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self._fetch_calls = 0
|
self._raise_on_fetch = False
|
||||||
self._fail_on_fetch_call_no = fail_on_fetch_call_no
|
|
||||||
|
|
||||||
def fetch_slot(self, slot_id):
|
def fetch_slot(self, slot_id):
|
||||||
self._fetch_calls += 1
|
if self._raise_on_fetch:
|
||||||
if self._fail_on_fetch_call_no == self._fetch_calls:
|
|
||||||
raise RuntimeError("context lookup failed")
|
raise RuntimeError("context lookup failed")
|
||||||
return super().fetch_slot(slot_id)
|
return super().fetch_slot(slot_id)
|
||||||
|
|
||||||
def fail_on_next_fetch(self):
|
|
||||||
self._fail_on_fetch_call_no = self._fetch_calls + 1
|
class FinalizeArmingIntentStore(InMemoryIntentStore):
|
||||||
|
"""Arm repo fetch failure immediately after finalize persistence."""
|
||||||
|
|
||||||
|
def __init__(self, repo):
|
||||||
|
super().__init__()
|
||||||
|
self._repo = repo
|
||||||
|
|
||||||
|
def finalize(self, intent_id, status, slot_id, reason):
|
||||||
|
super().finalize(intent_id, status, slot_id, reason)
|
||||||
|
self._repo._raise_on_fetch = True
|
||||||
|
|
||||||
|
|
||||||
def _make_holding_slot(repo, svc, instance_key="default", portfolio_id="pf-A", symbol="005930", qty=10):
|
def _make_holding_slot(repo, svc, instance_key="default", portfolio_id="pf-A", symbol="005930", qty=10):
|
||||||
@@ -531,10 +538,10 @@ def test_reconcile_warned_alarm_finalize_failure_and_publish_failure_are_isolate
|
|||||||
|
|
||||||
|
|
||||||
def test_reconcile_warned_buy_context_lookup_failure_does_not_change_response():
|
def test_reconcile_warned_buy_context_lookup_failure_does_not_change_response():
|
||||||
repo = ContextFailingSlotRepository(fail_on_fetch_call_no=2)
|
repo = PostFinalizeContextFailingSlotRepository()
|
||||||
svc = SlotService(repo)
|
svc = SlotService(repo)
|
||||||
warn_store = InMemoryReconcileWarningStore()
|
warn_store = InMemoryReconcileWarningStore()
|
||||||
intent_store = InMemoryIntentStore()
|
intent_store = FinalizeArmingIntentStore(repo)
|
||||||
alarms = RecordingAlarmEmitter()
|
alarms = RecordingAlarmEmitter()
|
||||||
repo.reconcile_universe("default", {"pf-A": "005930"})
|
repo.reconcile_universe("default", {"pf-A": "005930"})
|
||||||
slot = repo.find_slot_by_portfolio("default", "pf-A")
|
slot = repo.find_slot_by_portfolio("default", "pf-A")
|
||||||
@@ -558,13 +565,12 @@ def test_reconcile_warned_buy_context_lookup_failure_does_not_change_response():
|
|||||||
|
|
||||||
|
|
||||||
def test_reconcile_warned_sell_context_lookup_failure_does_not_change_response():
|
def test_reconcile_warned_sell_context_lookup_failure_does_not_change_response():
|
||||||
repo = ContextFailingSlotRepository()
|
repo = PostFinalizeContextFailingSlotRepository()
|
||||||
svc = SlotService(repo)
|
svc = SlotService(repo)
|
||||||
warn_store = InMemoryReconcileWarningStore()
|
warn_store = InMemoryReconcileWarningStore()
|
||||||
intent_store = InMemoryIntentStore()
|
intent_store = FinalizeArmingIntentStore(repo)
|
||||||
alarms = RecordingAlarmEmitter()
|
alarms = RecordingAlarmEmitter()
|
||||||
slot = _make_holding_slot(repo, svc, qty=10, portfolio_id="pf-A", symbol="005930")
|
slot = _make_holding_slot(repo, svc, qty=10, portfolio_id="pf-A", symbol="005930")
|
||||||
repo._fail_on_fetch_call_no = repo._fetch_calls + 2
|
|
||||||
warn_store.set_warned(str(slot.slot_id), portfolio_id="pf-A", case_code="C")
|
warn_store.set_warned(str(slot.slot_id), portfolio_id="pf-A", case_code="C")
|
||||||
intent_svc = IntentService(
|
intent_svc = IntentService(
|
||||||
svc, repo, intent_store, instance_key="default", warn_store=warn_store, alarm_emitter=alarms
|
svc, repo, intent_store, instance_key="default", warn_store=warn_store, alarm_emitter=alarms
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ where no broker connection exists. In that case publish_alarm() logs and skips.
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
import sys
|
|
||||||
from datetime import datetime, timedelta, timezone
|
from datetime import datetime, timedelta, timezone
|
||||||
|
|
||||||
from trex_core.adapters.messaging.topology import alarm_routing_key
|
from trex_core.adapters.messaging.topology import alarm_routing_key
|
||||||
@@ -91,11 +90,9 @@ class AlarmEmitter:
|
|||||||
code, title,
|
code, title,
|
||||||
)
|
)
|
||||||
return False
|
return False
|
||||||
except Exception: # noqa: BLE001 — alarm must never crash the caller
|
except Exception as exc: # noqa: BLE001 — alarm must never crash the caller
|
||||||
exc = sys.exc_info()[1]
|
|
||||||
exc_name = type(exc).__name__ if exc is not None else "Exception"
|
|
||||||
_log.error(
|
_log.error(
|
||||||
"alarm publish failed: code=%s title=%s error_type=%s",
|
"alarm publish failed: code=%s title=%s error_type=%s",
|
||||||
code, title, exc_name,
|
code, title, type(exc).__name__,
|
||||||
)
|
)
|
||||||
return False
|
return False
|
||||||
|
|||||||
Reference in New Issue
Block a user