forked from sass/tipibot
15 one-time achievements over the lifetime stat counters (work/wealth/gambling/ crime/heists/fishing/streaks/prestige), each paying a modest one-time coin reward when unlocked. Detection is lazy - opening /achievements claims any newly earned (like quests roll on view) - so no per-command hook is needed, and rewards are a bounded, one-time coin source. - New achievements.py: ACHIEVEMENTS table, pure newly_earned/achievements_view, and locked do_check_achievements. New achievements_earned schema field. - /achievements command (own view claims; viewing others is read-only) with progress bars, db_error handling, and an "unlocked" banner. 8 tests: threshold detection, one-time claim (no double pay), multi-unlock, view progress capping. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013VbAVsrZuYesea99mPMmPT
79 lines
3.0 KiB
Python
79 lines
3.0 KiB
Python
"""Tests for the achievements system (one-time milestone rewards)."""
|
|
|
|
from core import economy
|
|
|
|
from conftest import run
|
|
|
|
UID = 4747
|
|
|
|
|
|
def _fund(fake_pb, **stats) -> None:
|
|
run(economy.get_user(UID))
|
|
rec = fake_pb.record_for(UID)
|
|
rec.update(stats)
|
|
|
|
|
|
class TestNewlyEarned:
|
|
def test_threshold_met_is_newly_earned(self, fake_pb):
|
|
_fund(fake_pb, work_count=10)
|
|
user = run(economy.get_user(UID))
|
|
assert "work_10" in economy.newly_earned(user)
|
|
|
|
def test_below_threshold_not_earned(self, fake_pb):
|
|
_fund(fake_pb, work_count=9)
|
|
user = run(economy.get_user(UID))
|
|
assert "work_10" not in economy.newly_earned(user)
|
|
|
|
def test_already_claimed_not_repeated(self, fake_pb):
|
|
_fund(fake_pb, work_count=10, achievements_earned=["work_10"])
|
|
user = run(economy.get_user(UID))
|
|
assert "work_10" not in economy.newly_earned(user)
|
|
|
|
|
|
class TestClaim:
|
|
def test_claims_and_pays_reward_once(self, fake_pb):
|
|
_fund(fake_pb, work_count=10, balance=0)
|
|
reward = economy.ACHIEVEMENTS["work_10"]["reward"]
|
|
|
|
res = run(economy.do_check_achievements(UID))
|
|
assert res["ok"] and res["new"] == ["work_10"]
|
|
assert res["reward"] == reward
|
|
assert fake_pb.record_for(UID)["balance"] == reward
|
|
assert "work_10" in fake_pb.record_for(UID)["achievements_earned"]
|
|
|
|
# Second call: nothing new, no double pay.
|
|
res2 = run(economy.do_check_achievements(UID))
|
|
assert res2["new"] == [] and res2["reward"] == 0
|
|
assert fake_pb.record_for(UID)["balance"] == reward
|
|
|
|
def test_multiple_unlocked_at_once(self, fake_pb):
|
|
_fund(fake_pb, work_count=100, total_fish_caught=25, balance=0)
|
|
res = run(economy.do_check_achievements(UID))
|
|
# work_10, work_100 and fish_25 all cross at once
|
|
assert set(res["new"]) == {"work_10", "work_100", "fish_25"}
|
|
expected = sum(economy.ACHIEVEMENTS[a]["reward"] for a in res["new"])
|
|
assert res["reward"] == expected
|
|
assert fake_pb.record_for(UID)["balance"] == expected
|
|
|
|
def test_nothing_to_claim(self, fake_pb):
|
|
_fund(fake_pb, balance=500)
|
|
res = run(economy.do_check_achievements(UID))
|
|
assert res["ok"] and res["new"] == [] and res["reward"] == 0
|
|
assert fake_pb.record_for(UID)["balance"] == 500
|
|
|
|
|
|
class TestView:
|
|
def test_view_marks_earned_and_progress(self, fake_pb):
|
|
_fund(fake_pb, work_count=5, achievements_earned=["beg_50"])
|
|
rows = economy.achievements_view(run(economy.get_user(UID)))
|
|
by_id = {r["id"]: r for r in rows}
|
|
assert by_id["beg_50"]["earned"] is True
|
|
assert by_id["work_10"]["earned"] is False
|
|
assert by_id["work_10"]["progress"] == 5 # capped at goal
|
|
assert len(rows) == len(economy.ACHIEVEMENTS)
|
|
|
|
def test_progress_capped_at_goal(self, fake_pb):
|
|
_fund(fake_pb, work_count=9999)
|
|
rows = {r["id"]: r for r in economy.achievements_view(run(economy.get_user(UID)))}
|
|
assert rows["work_10"]["progress"] == rows["work_10"]["goal"]
|