"""Tests for pending-wager escrow persistence. Interactive games (blackjack, RPS PvP) deduct a stake up front and hold it in an in-memory View. These tests verify the stake is recorded on the user record in the same commit, cleared on settlement, and refunded by reconcile on restart. """ from core import economy from conftest import run UID = 555 OPP = 556 def _fund(fake_pb, uid: int, amount: int) -> None: run(economy.get_user(uid)) fake_pb.record_for(uid)["balance"] = amount def _pending(fake_pb, uid: int) -> dict: return fake_pb.record_for(uid).get("pending_wager") or {} class TestBlackjackEscrow: def test_bet_records_pending_and_payout_clears(self, fake_pb): _fund(fake_pb, UID, 1000) run(economy.do_blackjack_bet(UID, 100)) pw = _pending(fake_pb, UID) assert pw["kind"] == "blackjack" and pw["amount"] == 100 run(economy.do_blackjack_payout(UID, payout=200, total_invested=100)) assert _pending(fake_pb, UID) == {} # settled assert fake_pb.record_for(UID)["balance"] == 1000 - 100 + 200 def test_double_split_accumulates_escrow(self, fake_pb): _fund(fake_pb, UID, 1000) run(economy.do_blackjack_bet(UID, 100)) # initial run(economy.do_blackjack_bet(UID, 100)) # double / split adds a hand assert _pending(fake_pb, UID)["amount"] == 200 def test_losing_hand_clears_escrow(self, fake_pb): _fund(fake_pb, UID, 1000) run(economy.do_blackjack_bet(UID, 100)) run(economy.do_blackjack_payout(UID, payout=0, total_invested=100)) # bust/loss assert _pending(fake_pb, UID) == {} class TestRpsEscrow: def test_deposit_records_and_payout_forfeit_clear(self, fake_pb): _fund(fake_pb, UID, 500) _fund(fake_pb, OPP, 500) run(economy.do_rps_pvp_deposit(UID, 100)) run(economy.do_rps_pvp_deposit(OPP, 100)) assert _pending(fake_pb, UID)["kind"] == "rps" assert _pending(fake_pb, OPP)["amount"] == 100 run(economy.do_rps_pvp_payout(UID, 100)) # UID wins run(economy.do_rps_pvp_forfeit(OPP)) # OPP loses assert _pending(fake_pb, UID) == {} assert _pending(fake_pb, OPP) == {} assert fake_pb.record_for(UID)["balance"] == 500 - 100 + 200 assert fake_pb.record_for(OPP)["balance"] == 500 - 100 def test_refund_clears_escrow(self, fake_pb): _fund(fake_pb, UID, 500) run(economy.do_rps_pvp_deposit(UID, 100)) run(economy.do_rps_pvp_refund(UID, 100)) assert _pending(fake_pb, UID) == {} assert fake_pb.record_for(UID)["balance"] == 500 class TestReconcile: def test_refunds_interrupted_stakes(self, fake_pb): # Simulate a restart: two players left mid-game with escrowed stakes. _fund(fake_pb, UID, 400) run(economy.do_blackjack_bet(UID, 100)) # 300 left, 100 escrowed _fund(fake_pb, OPP, 500) fake_pb.record_for(OPP)["balance"] = 500 run(economy.do_rps_pvp_deposit(OPP, 250)) # 250 left, 250 escrowed refunded = run(economy.reconcile_pending_wagers()) by_uid = {uid: (amt, kind) for uid, amt, kind in refunded} assert by_uid[UID] == (100, "blackjack") assert by_uid[OPP] == (250, "rps") assert fake_pb.record_for(UID)["balance"] == 400 # stake restored assert fake_pb.record_for(OPP)["balance"] == 500 assert _pending(fake_pb, UID) == {} assert _pending(fake_pb, OPP) == {} def test_noop_when_nothing_pending(self, fake_pb): _fund(fake_pb, UID, 100) assert run(economy.reconcile_pending_wagers()) == [] assert fake_pb.record_for(UID)["balance"] == 100 def test_reconcile_is_idempotent(self, fake_pb): _fund(fake_pb, UID, 400) run(economy.do_blackjack_bet(UID, 100)) run(economy.reconcile_pending_wagers()) # A second run (e.g. another restart) must not double-refund. assert run(economy.reconcile_pending_wagers()) == [] assert fake_pb.record_for(UID)["balance"] == 400