143 lines
5.4 KiB
Python
143 lines
5.4 KiB
Python
"""
|
|
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")
|