forked from sass/tipibot
A pay-to-open box (1000 coins) with a weighted reward: coin tiers (usually a net loss - the sink), a random 30-min earn/exp buff, or a rare jackpot. All rolling lives in do_open_lootbox for testability; the command adds a short reveal. - New core module lootbox.py; extracted consumables.grant_buff (reused by both consumables and lootbox) to avoid duplicating the buff-stacking logic. - New lootboxes_opened stat; pending schema syncs as a number automatically. - Added /consumables, /lootbox, /vanity to the help embed (the first two were previously missing from /help). Tests cover charging, insufficient/banned, the coin and buff outcomes, the net-vs-reward invariant, and that balance never goes negative. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013VbAVsrZuYesea99mPMmPT
79 lines
2.7 KiB
Python
79 lines
2.7 KiB
Python
"""Tests for the /lootbox mystery box (pay-to-open coin sink)."""
|
|
|
|
import random
|
|
|
|
from core import economy
|
|
|
|
from conftest import run
|
|
|
|
UID = 8080
|
|
|
|
|
|
def _fund(fake_pb, amount: int) -> None:
|
|
run(economy.get_user(UID))
|
|
fake_pb.record_for(UID)["balance"] = amount
|
|
|
|
|
|
def _open_until(fake_pb, predicate):
|
|
"""Open boxes with varied seeds until `predicate(res)` holds; returns that res."""
|
|
for seed in range(1000):
|
|
random.seed(seed)
|
|
_fund(fake_pb, 100_000)
|
|
res = run(economy.do_open_lootbox(UID))
|
|
if predicate(res):
|
|
return res
|
|
raise AssertionError("outcome never occurred")
|
|
|
|
|
|
class TestOpen:
|
|
def test_costs_the_fee_and_charges_up_front(self, fake_pb):
|
|
_fund(fake_pb, 1_000)
|
|
random.seed(1)
|
|
res = run(economy.do_open_lootbox(UID))
|
|
assert res["ok"]
|
|
# balance == 1000 - cost + reward_coins
|
|
assert res["balance"] == 1_000 - economy.LOOTBOX_COST + res["reward_coins"]
|
|
|
|
def test_insufficient_rejected(self, fake_pb):
|
|
_fund(fake_pb, economy.LOOTBOX_COST - 1)
|
|
res = run(economy.do_open_lootbox(UID))
|
|
assert not res["ok"] and res["reason"] == "insufficient"
|
|
assert res["need"] == 1
|
|
assert fake_pb.record_for(UID)["balance"] == economy.LOOTBOX_COST - 1 # not charged
|
|
|
|
def test_banned_rejected(self, fake_pb):
|
|
_fund(fake_pb, 5_000)
|
|
fake_pb.record_for(UID)["eco_banned"] = True
|
|
res = run(economy.do_open_lootbox(UID))
|
|
assert not res["ok"] and res["reason"] == "banned"
|
|
|
|
def test_increments_counter(self, fake_pb):
|
|
_fund(fake_pb, 5_000)
|
|
random.seed(3)
|
|
run(economy.do_open_lootbox(UID))
|
|
run(economy.do_open_lootbox(UID))
|
|
assert run(economy.get_user(UID))["lootboxes_opened"] == 2
|
|
|
|
|
|
class TestOutcomes:
|
|
def test_coin_outcome_credits_and_net_matches(self, fake_pb):
|
|
res = _open_until(fake_pb, lambda r: r["reward_coins"] > 0)
|
|
assert res["net"] == res["reward_coins"] - economy.LOOTBOX_COST
|
|
assert res["buff_kind"] is None
|
|
|
|
def test_buff_outcome_grants_active_buff(self, fake_pb):
|
|
res = _open_until(fake_pb, lambda r: r["buff_kind"] is not None)
|
|
assert res["reward_coins"] == 0
|
|
assert res["net"] == -economy.LOOTBOX_COST
|
|
user = run(economy.get_user(UID))
|
|
# the granted buff is live
|
|
assert res["buff_kind"] in economy.active_buffs(user)
|
|
|
|
def test_never_negative_balance(self, fake_pb):
|
|
# Open exactly at the cost floor repeatedly; balance must stay >= 0.
|
|
for seed in range(30):
|
|
random.seed(seed)
|
|
_fund(fake_pb, economy.LOOTBOX_COST)
|
|
res = run(economy.do_open_lootbox(UID))
|
|
assert res["balance"] >= 0
|