Publish an SSE update from the bet/withdrawal rollback paths (B-49)
_release_failed_bet and _release_failed_withdrawal restored the balance, freed the reserved UTXOs and (for a bet) removed the participant without calling broadcaster.publish(), so every dashboard kept showing the phantom bet and the reduced balance until its next poll — while the success path and the reconciler's own abandon path both published. The two regression tests pre-open the round before subscribing: place_bet opens one itself, and that publish() would otherwise satisfy the assertion whether or not the rollback published anything.
This commit is contained in:
@@ -8,6 +8,7 @@ from app.bets.service import BetError, place_bet
|
||||
from app.config import settings
|
||||
from app.db.base import Base
|
||||
from app.db.models import AuditLog, PendingTransaction, Round, RoundConfig, RoundParticipant, User, UtxoEvent
|
||||
from app.rounds.events import broadcaster
|
||||
from app.rounds.service import open_new_round_if_needed
|
||||
from app.wallet.hd import derive_user_address
|
||||
from app.wallet.psbt_builder import MAX_TX_INPUTS
|
||||
@@ -196,6 +197,33 @@ async def test_failed_broadcast_leaves_nothing_behind(session_factory):
|
||||
assert "bet_placed" not in events
|
||||
|
||||
|
||||
async def test_failed_broadcast_publishes_an_sse_update(session_factory): # B-49
|
||||
"""The rollback moves as much state as the successful path does, so it must ping
|
||||
the dashboards the same way — otherwise the phantom bet stays on screen until the
|
||||
next poll."""
|
||||
user_id = await _make_funded_user(session_factory, 21, 3_000_000_000)
|
||||
async with session_factory() as session:
|
||||
# Open the round up front: place_bet would otherwise open it itself, and that
|
||||
# publish() would satisfy the assertion below whether or not the rollback ever
|
||||
# published one of its own.
|
||||
await open_new_round_if_needed(session)
|
||||
await session.commit()
|
||||
|
||||
queue = broadcaster.subscribe()
|
||||
try:
|
||||
while not queue.empty():
|
||||
queue.get_nowait()
|
||||
|
||||
async with session_factory() as session:
|
||||
user = await session.get(User, user_id)
|
||||
with pytest.raises(BetError, match="refused"):
|
||||
await place_bet(session, RejectingElectrumClient(), user)
|
||||
|
||||
assert not queue.empty()
|
||||
finally:
|
||||
broadcaster.unsubscribe(queue)
|
||||
|
||||
|
||||
async def test_failed_broadcast_reports_the_broadcast_failed_code(session_factory):
|
||||
user_id = await _make_funded_user(session_factory, 5, 3_000_000_000)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user