"""Tests for the daily lottery (buy tickets, weighted winner takes the pot).""" import datetime import random from zoneinfo import ZoneInfo from core import economy from conftest import run TZ = ZoneInfo("Europe/Tallinn") P = "2026-09-04" # a fixed draw period UID = 3131 UID2 = 3132 UID3 = 3133 def _fund(fake_pb, uid: int, balance: int) -> None: run(economy.get_user(uid)) fake_pb.record_for(uid)["balance"] = balance class TestPeriod: def test_before_draw_hour_is_today(self): dt = datetime.datetime(2026, 9, 4, 20, 0, tzinfo=TZ) assert economy.period_for(dt) == "2026-09-04" def test_at_or_after_draw_hour_is_tomorrow(self): dt = datetime.datetime(2026, 9, 4, economy.DRAW_HOUR, 0, tzinfo=TZ) assert economy.period_for(dt) == "2026-09-05" class TestBuy: def test_buy_deducts_and_records_tickets(self, fake_pb): _fund(fake_pb, UID, 10_000) res = run(economy.do_buy_ticket(UID, 3, P)) assert res["ok"] and res["tickets"] == 3 assert res["cost"] == 3 * economy.TICKET_COST assert res["balance"] == 10_000 - 3 * economy.TICKET_COST rec = fake_pb.record_for(UID) assert rec["lottery_tickets"] == 3 and rec["lottery_period"] == P def test_buy_accumulates_same_period(self, fake_pb): _fund(fake_pb, UID, 10_000) run(economy.do_buy_ticket(UID, 2, P)) res = run(economy.do_buy_ticket(UID, 3, P)) assert res["tickets"] == 5 def test_new_period_resets_tickets(self, fake_pb): _fund(fake_pb, UID, 10_000) run(economy.do_buy_ticket(UID, 5, P)) res = run(economy.do_buy_ticket(UID, 1, "2026-09-05")) assert res["tickets"] == 1 # old period's tickets dropped def test_insufficient_rejected(self, fake_pb): _fund(fake_pb, UID, 100) res = run(economy.do_buy_ticket(UID, 1, P)) assert not res["ok"] and res["reason"] == "insufficient" assert fake_pb.record_for(UID)["balance"] == 100 def test_max_tickets_enforced(self, fake_pb): _fund(fake_pb, UID, 10_000_000) res = run(economy.do_buy_ticket(UID, economy.MAX_TICKETS_PER_DRAW + 1, P)) assert not res["ok"] and res["reason"] == "max_tickets" def test_nonpositive_rejected(self, fake_pb): _fund(fake_pb, UID, 10_000) assert run(economy.do_buy_ticket(UID, 0, P))["reason"] == "invalid" def test_banned_rejected(self, fake_pb): _fund(fake_pb, UID, 10_000) fake_pb.record_for(UID)["eco_banned"] = True assert run(economy.do_buy_ticket(UID, 1, P))["reason"] == "banned" class TestState: def test_pot_and_your_tickets(self, fake_pb): _fund(fake_pb, UID, 10_000) _fund(fake_pb, UID2, 10_000) run(economy.do_buy_ticket(UID, 3, P)) run(economy.do_buy_ticket(UID2, 2, P)) state = run(economy.get_lottery_state(P, UID)) assert state["total_tickets"] == 5 assert state["pot"] == 5 * economy.TICKET_COST assert state["participants"] == 2 assert state["your_tickets"] == 3 def test_other_period_not_counted(self, fake_pb): _fund(fake_pb, UID, 10_000) run(economy.do_buy_ticket(UID, 3, "2026-01-01")) state = run(economy.get_lottery_state(P)) assert state["total_tickets"] == 0 and state["pot"] == 0 class TestDraw: def test_no_participants_returns_none(self, fake_pb): assert run(economy.do_lottery_draw(P)) is None def test_winner_gets_whole_pot_and_coins_conserved(self, fake_pb): _fund(fake_pb, UID, 10_000) _fund(fake_pb, UID2, 10_000) run(economy.do_buy_ticket(UID, 3, P)) # -600 run(economy.do_buy_ticket(UID2, 2, P)) # -400 pot = 5 * economy.TICKET_COST total_before = sum(fake_pb.record_for(u)["balance"] for u in (UID, UID2)) random.seed(1) res = run(economy.do_lottery_draw(P)) assert res["ok"] and res["pot"] == pot winner, loser = (UID, UID2) if res["winner_id"] == UID else (UID2, UID) assert fake_pb.record_for(winner)["balance"] == ( (10_000 - (3 if winner == UID else 2) * economy.TICKET_COST) + pot ) # Coins conserved: the pot minted to the winner equals total ticket spend. total_after = sum(fake_pb.record_for(u)["balance"] for u in (UID, UID2)) assert total_after == total_before + pot assert fake_pb.record_for(res["winner_id"])["lottery_tickets"] == 0 # consumed def test_more_tickets_wins_more_often(self, fake_pb): _fund(fake_pb, UID, 10_000_000) _fund(fake_pb, UID2, 10_000_000) wins = {UID: 0, UID2: 0} for seed in range(200): # reset tickets each round to the same split fake_pb.record_for(UID).update(lottery_tickets=9, lottery_period=P) fake_pb.record_for(UID2).update(lottery_tickets=1, lottery_period=P) random.seed(seed) res = run(economy.do_lottery_draw(P)) wins[res["winner_id"]] += 1 # UID holds 90% of tickets -> should win far more often. assert wins[UID] > wins[UID2] * 3