Refuse to open a round that could not pay its winner (B-66)
fee_address has no column default, because an operator has to supply their own — and the payout pays the 30% commission to it, so build_payout_transaction cannot even be built without one. A fresh instance nonetheless opened rounds happily: each took bets, confirmed them, and only then discovered it was unpayable, wedging in "paying_out" and retrying every 60s with money already in the pool. One manual recovery per round, until somebody noticed. open_new_round_if_needed now checks rounds_can_open(config) alongside `paused`: no payout address, no round. Nothing has moved yet at that point, which is the whole difference. Same scope as pausing — a round already in progress still closes, draws and pays out, since clearing the address mid-round is exactly the operator slip that must not strand a live round. Surfaced rather than silent, in the two places that matter: lottery_configured on GET /rounds/current, which makes / show a *different* banner from the maintenance one (telling a player "come back later" would be false — nothing is coming until setup finishes), and a warning at the top of /admin's Parametri card, the one screen that can fix it. rounds_can_open is where any future would-make-a-round-unpayable prerequisite belongs, instead of being discovered at payout time. The test churn is the finding restated: 26 tests expected a round to open on an instance with no payout address. Their fixtures now seed one, so each goes back to testing what it says — several would otherwise have passed for the wrong reason, returning None because of the missing address rather than because of the cooldown or pause under test. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -192,6 +192,34 @@ async def test_no_pending_bets_reported_once_every_bet_has_confirmed(client): #
|
||||
assert body["pending_jackpot_sats"] == body["jackpot_sats"]
|
||||
|
||||
|
||||
async def test_lottery_configured_flags_a_missing_fee_address(client): # B-66
|
||||
"""The frontend has to tell "the next round is coming" apart from "nothing is
|
||||
coming until the operator finishes setting this up" — the banner says different
|
||||
things, and only one of them is worth waiting for."""
|
||||
from sqlalchemy import select
|
||||
|
||||
from app.db.models import RoundConfig
|
||||
|
||||
ac, session_factory = client
|
||||
|
||||
async with session_factory() as session:
|
||||
session.add(RoundConfig(fee_address=""))
|
||||
await session.commit()
|
||||
|
||||
body = (await ac.get("/rounds/current")).json()
|
||||
assert body["lottery_configured"] is False
|
||||
assert body["lottery_paused"] is False # not a pause: a prerequisite that isn't met
|
||||
assert body["round_id"] is None # and indeed no round was opened
|
||||
|
||||
async with session_factory() as session:
|
||||
(await session.scalars(select(RoundConfig))).one().fee_address = (
|
||||
"plm1q5x25wd6q463mfhckjraaedgjg0lyu73qfcj43n"
|
||||
)
|
||||
await session.commit()
|
||||
|
||||
assert (await ac.get("/rounds/current")).json()["lottery_configured"] is True
|
||||
|
||||
|
||||
async def test_draw_waiting_since_is_exposed_only_while_drawing(client):
|
||||
"""B-36: the "drawing" wait on a future block has no timeout, so the frontend
|
||||
needs draw_waiting_since to show "still waiting" instead of implying a bounded
|
||||
|
||||
Reference in New Issue
Block a user