Files
tipibot/tests/test_vanity.py
Rene Arumetsa 037628d24f feat(economy): add vanity shop and harden economy money-safety
Vanity shop (/vanity): cosmetic badges/titles as a pure whale coin sink -
purchases burn coins (not credited to the house) and equip a badge shown on
/profile. New vanity_owned/vanity_active fields, schema sync, and tests.

Money-safety and robustness fixes from a codebase review:

- _parse_amount now rejects negative amounts. Every bet/give/request flows
  through it, so a negative value can no longer mint coins on a loss/transfer
  path that trusts the caller's sign (all call sites already guarded <= 0;
  this closes the source).
- do_blackjack_payout no longer raises on a DB failure. The stake was already
  deducted in do_blackjack_bet, so it now logs critical with the owed amount
  (for admin reconciliation) and returns db_error; all payout call sites render
  a clear "payout failed" notice instead of crashing the interaction.
- Instant "kohv" consumable now cancels the pending reminder DMs for the
  cooldowns it wipes (via new INSTANT_RESET_COMMANDS), so no stale/duplicate
  reminders fire.
- Renamed the misleadingly-named _refund_user_safe -> _debit_house_safe (it
  debits the house) and dropped its ignored first arg.
- Added __all__ to vanity.py and consumables.py so `import *` no longer leaks
  incidental imports into the economy namespace.
- Documented Kõrvaklapid's +25 coin daily bonus in README and DEV_NOTES.

Tests: blackjack payout DB-failure safety and INSTANT_RESET_COMMANDS lockstep.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013VbAVsrZuYesea99mPMmPT
2026-09-04 02:09:25 +03:00

79 lines
3.1 KiB
Python

"""Tests for the vanity shop (cosmetic status sink, no gameplay effect)."""
from core import economy
from conftest import run
UID = 7777
def _fund(fake_pb, amount: int) -> None:
run(economy.get_user(UID))
fake_pb.record_for(UID)["balance"] = amount
class TestBuy:
def test_buy_burns_coins_owns_and_equips(self, fake_pb):
_fund(fake_pb, 10_000)
cost = economy.VANITY["couch"]["cost"]
res = run(economy.do_vanity_select(UID, "couch"))
assert res["ok"] and res["action"] == "bought"
assert res["balance"] == 10_000 - cost
user = run(economy.get_user(UID))
assert "couch" in user["vanity_owned"]
assert user["vanity_active"] == "couch"
assert economy.vanity_badge(user) == ("🎮", "Sohvasõdur")
def test_coins_are_destroyed_not_sent_to_house(self, fake_pb, monkeypatch):
_fund(fake_pb, 10_000)
credited = []
monkeypatch.setattr(economy.house, "_credit_house", lambda amt: credited.append(amt))
run(economy.do_vanity_select(UID, "couch"))
assert credited == [] # nothing recirculated to the house
def test_insufficient_funds_rejected(self, fake_pb):
_fund(fake_pb, 100)
res = run(economy.do_vanity_select(UID, "legend"))
assert not res["ok"] and res["reason"] == "insufficient"
assert res["need"] == economy.VANITY["legend"]["cost"] - 100
assert run(economy.get_user(UID))["balance"] == 100 # unchanged
def test_banned_rejected(self, fake_pb):
_fund(fake_pb, 10_000)
fake_pb.record_for(UID)["eco_banned"] = True
res = run(economy.do_vanity_select(UID, "couch"))
assert not res["ok"] and res["reason"] == "banned"
def test_unknown_badge(self, fake_pb):
_fund(fake_pb, 10_000)
res = run(economy.do_vanity_select(UID, "nope"))
assert not res["ok"] and res["reason"] == "not_found"
class TestEquip:
def test_equip_owned_is_free(self, fake_pb):
_fund(fake_pb, 20_000)
run(economy.do_vanity_select(UID, "couch")) # buy + equip couch
run(economy.do_vanity_select(UID, "cables")) # buy + equip cables
bal_before = run(economy.get_user(UID))["balance"]
res = run(economy.do_vanity_select(UID, "couch")) # re-equip owned couch
assert res["ok"] and res["action"] == "equipped"
user = run(economy.get_user(UID))
assert user["vanity_active"] == "couch"
assert user["balance"] == bal_before # no re-charge
def test_unequip_clears_badge(self, fake_pb):
_fund(fake_pb, 10_000)
run(economy.do_vanity_select(UID, "couch"))
res = run(economy.do_vanity_select(UID, economy.vanity.NONE_ID))
assert res["ok"] and res["action"] == "unequipped"
user = run(economy.get_user(UID))
assert user["vanity_active"] is None
assert economy.vanity_badge(user) is None
assert "couch" in user["vanity_owned"] # still owned, just not worn
def test_no_badge_by_default(fake_pb):
user = run(economy.get_user(UID))
assert economy.vanity_badge(user) is None