""" tests/test_odds.py ------------------ Tests for core/odds.py — combinatorics-based prize-odds calculator. Reference values: Powerball 5/69 + 1/26 → total 292,201,338 Mega Millions 5/70 + 1/25 → total 302,575,350 UK-style 6/49 no bonus → total 13,983,816 """ import pytest from core.odds import total_combinations, prize_odds, game_odds from db.models import get_game_by_name, add_game # ── total_combinations ──────────────────────────────────────────────────────── def test_total_powerball(tmp_db): assert total_combinations(5, 69, 1, 26) == 292_201_338 def test_total_mega_millions(tmp_db): assert total_combinations(5, 70, 1, 25) == 302_575_350 def test_total_no_bonus(tmp_db): # C(49, 6) = 13,983,816 assert total_combinations(6, 49, 0, 0) == 13_983_816 def test_total_single_ball_no_bonus(tmp_db): # C(10, 1) = 10 assert total_combinations(1, 10, 0, 0) == 10 # ── prize_odds — Powerball (5/69 + 1/26) ───────────────────────────────────── def test_powerball_jackpot_odds(tmp_db): rows = prize_odds(5, 69, 1, 26) jackpot = next(r for r in rows if r["tier"] == "Jackpot") assert jackpot["ways"] == 1 assert jackpot["odds"] == pytest.approx(292_201_338, rel=1e-6) def test_powerball_match5_odds(tmp_db): rows = prize_odds(5, 69, 1, 26) m5 = next(r for r in rows if r["tier"] == "Match 5") assert m5["ways"] == 25 assert m5["odds"] == pytest.approx(11_688_053.52, rel=1e-4) def test_powerball_match4_bonus_odds(tmp_db): rows = prize_odds(5, 69, 1, 26) m4b = next(r for r in rows if r["tier"] == "Match 4 + Bonus") assert m4b["ways"] == 320 assert m4b["odds"] == pytest.approx(913_129.18, rel=1e-4) def test_powerball_bonus_only_odds(tmp_db): rows = prize_odds(5, 69, 1, 26) bo = next(r for r in rows if r["tier"] == "Bonus Only") # C(5,0)*C(64,5)*1 = 7,624,512 ways assert bo["ways"] == 7_624_512 assert bo["odds"] == pytest.approx(38.32, rel=1e-3) def test_powerball_tier_count(tmp_db): rows = prize_odds(5, 69, 1, 26) # Jackpot, Match5, M4+B, M4, M3+B, M3, M2+B, M2, M1+B, M1, BonusOnly = 11 assert len(rows) == 11 def test_powerball_tiers_sorted_hardest_first(tmp_db): rows = prize_odds(5, 69, 1, 26) odds_list = [r["odds"] for r in rows] assert odds_list == sorted(odds_list, reverse=True) def test_powerball_probabilities_sum_to_less_than_one(tmp_db): rows = prize_odds(5, 69, 1, 26) total_prob = sum(r["probability"] for r in rows) assert total_prob < 1.0 def test_powerball_ways_sum_lte_total(tmp_db): rows = prize_odds(5, 69, 1, 26) total = rows[0]["total"] assert sum(r["ways"] for r in rows) <= total # ── prize_odds — no-bonus game (6/49) ──────────────────────────────────────── def test_no_bonus_jackpot_odds(tmp_db): rows = prize_odds(6, 49, 0, 0) jackpot = next(r for r in rows if r["tier"] == "Jackpot") assert jackpot["ways"] == 1 assert jackpot["odds"] == pytest.approx(13_983_816, rel=1e-6) def test_no_bonus_tier_count(tmp_db): # Jackpot + Match5 + Match4 + Match3 + Match2 + Match1 = 6 rows = prize_odds(6, 49, 0, 0) assert len(rows) == 6 def test_no_bonus_no_bonus_tiers(tmp_db): rows = prize_odds(6, 49, 0, 0) assert all("Bonus" not in r["tier"] for r in rows) def test_no_bonus_tier_labels(tmp_db): rows = prize_odds(6, 49, 0, 0) labels = [r["tier"] for r in rows] assert labels == ["Jackpot", "Match 5", "Match 4", "Match 3", "Match 2", "Match 1"] # ── prize_odds — Mega Millions (5/70 + 1/25) ───────────────────────────────── def test_mega_millions_jackpot_odds(tmp_db): rows = prize_odds(5, 70, 1, 25) jackpot = next(r for r in rows if r["tier"] == "Jackpot") assert jackpot["odds"] == pytest.approx(302_575_350, rel=1e-6) # ── game_odds (DB-backed) ───────────────────────────────────────────────────── def test_game_odds_powerball(tmp_db): game = get_game_by_name("Powerball") rows = game_odds(game["id"]) assert len(rows) == 11 jackpot = next(r for r in rows if r["tier"] == "Jackpot") assert jackpot["odds"] == pytest.approx(292_201_338, rel=1e-6) def test_game_odds_invalid_id(tmp_db): assert game_odds(99999) == [] def test_game_odds_custom_no_bonus(tmp_db): gid = add_game("UK Style", 6, 49, bonus_count=0, bonus_max=0) rows = game_odds(gid) assert len(rows) == 6 jackpot = next(r for r in rows if r["tier"] == "Jackpot") assert jackpot["odds"] == pytest.approx(13_983_816, rel=1e-6) # ── odds_str formatting ─────────────────────────────────────────────────────── def test_jackpot_odds_str_integer(tmp_db): rows = prize_odds(5, 69, 1, 26) jackpot = next(r for r in rows if r["tier"] == "Jackpot") assert jackpot["odds_str"] == "1 in 292,201,338" def test_match5_odds_str_decimal(tmp_db): rows = prize_odds(5, 69, 1, 26) m5 = next(r for r in rows if r["tier"] == "Match 5") assert m5["odds_str"].startswith("1 in 11,688,053")