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:
copilot-swe-agent[bot]
2026-08-26 05:07:35 +00:00
committed by GitHub
co-authored by genichin
parent 1ca9e60c98
commit 75406af970
2 changed files with 22 additions and 19 deletions
@@ -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
+2 -5
View File
@@ -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