Scan survives score-refresh failures; fix expired-ORM crash after rollback

A scoring error no longer skips setup detection for the ticker: the
rollback already restores a clean transaction, and qualification
re-gates on live scores at alert time, so a stale score is recoverable
but a skipped scan is not.

Iterating symbol strings instead of Ticker instances fixes a latent
crash the new regression test caught: rollback() expires ORM objects
regardless of expire_on_commit, so touching ticker.symbol in the except
handler triggered sync lazy-loading, which raises on an AsyncSession
and killed the whole scan on the first per-ticker error.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-11 16:29:06 +02:00
co-authored by Claude Fable 5
parent 9450831ef3
commit 292b9934b1
2 changed files with 80 additions and 15 deletions
+61
View File
@@ -0,0 +1,61 @@
"""Tests for scan_all_tickers orchestration: error isolation between phases."""
from __future__ import annotations
import pytest
from app.models.ticker import Ticker
from app.services import rr_scanner_service, scoring_service
from tests.conftest import _test_session_factory # type: ignore
@pytest.fixture
async def session():
async with _test_session_factory() as s:
yield s
async def test_scan_proceeds_when_score_refresh_fails(session, monkeypatch):
"""A scoring failure must not skip setup detection for the ticker.
Qualification re-gates on live scores at alert time, so a stale score is
recoverable — a skipped scan is not.
"""
session.add(Ticker(symbol="AAA"))
await session.commit()
async def _boom(db, symbol):
raise RuntimeError("scoring unavailable")
scanned: list[str] = []
async def _fake_scan_ticker(db, symbol, *args, **kwargs):
scanned.append(symbol)
return []
monkeypatch.setattr(scoring_service, "compute_all_dimensions", _boom)
monkeypatch.setattr(rr_scanner_service, "scan_ticker", _fake_scan_ticker)
setups = await rr_scanner_service.scan_all_tickers(session)
assert scanned == ["AAA"]
assert setups == []
async def test_scan_error_does_not_stop_later_tickers(session, monkeypatch):
session.add_all([Ticker(symbol="AAA"), Ticker(symbol="BBB")])
await session.commit()
scanned: list[str] = []
async def _fake_scan_ticker(db, symbol, *args, **kwargs):
scanned.append(symbol)
if symbol == "AAA":
raise RuntimeError("scan blew up")
return []
monkeypatch.setattr(rr_scanner_service, "scan_ticker", _fake_scan_ticker)
await rr_scanner_service.scan_all_tickers(session)
assert scanned == ["AAA", "BBB"]