Distinguish a pending-only balance from a truly insufficient one (B-37)
request_withdrawal validated against confirmed UTXOs only and answered a flat insufficient_balance even when the requested amount was covered by the pending-inclusive balance the UI actually shows (unconfirmed change from a recent bet/withdrawal) — contradicting what the user was looking at on screen. Raise balance_pending_confirmation instead when compute_pending_balance covers the amount, carrying the pending sats in params, with its error.* string in all 7 languages. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -2,6 +2,7 @@ import pytest
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy.ext.asyncio import async_sessionmaker, create_async_engine
|
||||
|
||||
from app.bets.service import place_bet
|
||||
from app.config import settings
|
||||
from app.db.base import Base
|
||||
from app.db.models import PendingTransaction, User, UtxoEvent, Withdrawal
|
||||
@@ -99,6 +100,35 @@ async def test_withdrawal_rejects_insufficient_balance(session_factory):
|
||||
await request_withdrawal(session, client, user, EXTERNAL_ADDRESS, BET_AMOUNT_SATS)
|
||||
|
||||
|
||||
async def test_withdrawal_distinguishes_pending_from_truly_insufficient_balance(session_factory):
|
||||
"""B-37: right after a bet, cached_balance_sats is ~0 because the whole funding
|
||||
UTXO was spent as input and the change hasn't confirmed yet — but the UI shows
|
||||
the pending-inclusive balance (compute_pending_balance), which does cover a
|
||||
withdrawal of this size. The error must say "not confirmed yet", not flatly
|
||||
"insufficient balance", or it contradicts what the user is looking at."""
|
||||
user_id = await _make_funded_user(session_factory, 4, 3_000_000_000)
|
||||
bet_client = FakeElectrumClient()
|
||||
|
||||
async with session_factory() as session:
|
||||
user = await session.get(User, user_id)
|
||||
await place_bet(session, bet_client, user)
|
||||
|
||||
async with session_factory() as session:
|
||||
user = await session.get(User, user_id)
|
||||
assert user.cached_balance_sats == 0 # the whole funding UTXO was spent as input
|
||||
|
||||
withdraw_client = FakeElectrumClient()
|
||||
with pytest.raises(WithdrawalError) as exc_info:
|
||||
# Above the withdrawal minimum (BET_AMOUNT_SATS) and covered by the
|
||||
# unconfirmed change (~1_999_800_000 sats), but not by the (zero)
|
||||
# confirmed balance.
|
||||
await request_withdrawal(session, withdraw_client, user, EXTERNAL_ADDRESS, 1_500_000_000)
|
||||
|
||||
assert exc_info.value.code == "balance_pending_confirmation"
|
||||
assert exc_info.value.params["pending_sats"] > 0
|
||||
assert not withdraw_client.broadcasted
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"address",
|
||||
[
|
||||
|
||||
Reference in New Issue
Block a user