05/23 Phase 14
This commit is contained in:
@@ -0,0 +1,161 @@
|
||||
"""
|
||||
tests/test_checker.py
|
||||
----------------------
|
||||
Tests for core/checker.py — ticket checking and prize tier logic.
|
||||
|
||||
Draw data (Powerball):
|
||||
Draw 1 2024-01-01: [1, 13, 36, 61, 69] bonus=7
|
||||
Draw 2 2024-01-03: [1, 2, 13, 45, 69] bonus=15
|
||||
Draw 3 2024-01-05: [2, 13, 22, 36, 55] bonus=3
|
||||
"""
|
||||
|
||||
import pytest
|
||||
from db.models import get_game_by_name, insert_draw
|
||||
from core.checker import check_ticket, prize_tier, parse_numbers
|
||||
|
||||
DRAWS = [
|
||||
("2024-01-01", [1, 13, 36, 61, 69], 7),
|
||||
("2024-01-03", [1, 2, 13, 45, 69], 15),
|
||||
("2024-01-05", [2, 13, 22, 36, 55], 3),
|
||||
]
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def pb(tmp_db):
|
||||
game = get_game_by_name("Powerball")
|
||||
for date, nums, bonus in DRAWS:
|
||||
insert_draw(game["id"], date, nums, bonus=bonus)
|
||||
return game
|
||||
|
||||
|
||||
# ── prize_tier ────────────────────────────────────────────────────────────────
|
||||
|
||||
def test_jackpot(tmp_db):
|
||||
assert prize_tier(5, True) == "Jackpot"
|
||||
|
||||
def test_match_5(tmp_db):
|
||||
assert prize_tier(5, False) == "Match 5"
|
||||
|
||||
def test_match_4_bonus(tmp_db):
|
||||
assert prize_tier(4, True) == "Match 4 + Bonus"
|
||||
|
||||
def test_match_4(tmp_db):
|
||||
assert prize_tier(4, False) == "Match 4"
|
||||
|
||||
def test_match_3_bonus(tmp_db):
|
||||
assert prize_tier(3, True) == "Match 3 + Bonus"
|
||||
|
||||
def test_match_3(tmp_db):
|
||||
assert prize_tier(3, False) == "Match 3"
|
||||
|
||||
def test_match_2_bonus(tmp_db):
|
||||
assert prize_tier(2, True) == "Match 2 + Bonus"
|
||||
|
||||
def test_match_1_bonus(tmp_db):
|
||||
assert prize_tier(1, True) == "Match 1 + Bonus"
|
||||
|
||||
def test_bonus_only(tmp_db):
|
||||
assert prize_tier(0, True) == "Bonus Only"
|
||||
|
||||
def test_no_prize(tmp_db):
|
||||
assert prize_tier(0, False) == "No Prize"
|
||||
|
||||
def test_no_prize_2_no_bonus(tmp_db):
|
||||
assert prize_tier(2, False) == "No Prize"
|
||||
|
||||
def test_no_prize_1_no_bonus(tmp_db):
|
||||
assert prize_tier(1, False) == "No Prize"
|
||||
|
||||
|
||||
# ── check_ticket — exact match ────────────────────────────────────────────────
|
||||
|
||||
def test_jackpot_ticket_found(pb):
|
||||
results = check_ticket(pb["id"], [1, 13, 36, 61, 69], bonus=7)
|
||||
assert len(results) >= 1
|
||||
top = results[0]
|
||||
assert top["main_matches"] == 5
|
||||
assert top["bonus_match"] is True
|
||||
assert top["prize_tier"] == "Jackpot"
|
||||
assert top["draw_date"] == "2024-01-01"
|
||||
|
||||
|
||||
def test_match_5_no_bonus(pb):
|
||||
results = check_ticket(pb["id"], [1, 13, 36, 61, 69], bonus=99)
|
||||
top = results[0]
|
||||
assert top["main_matches"] == 5
|
||||
assert top["bonus_match"] is False
|
||||
assert top["prize_tier"] == "Match 5"
|
||||
|
||||
|
||||
def test_partial_match_3(pb):
|
||||
# Numbers 1, 13, 69 appear in draw 1 and draw 2
|
||||
results = check_ticket(pb["id"], [1, 13, 69, 4, 8])
|
||||
dates_matched = {r["draw_date"] for r in results}
|
||||
assert "2024-01-01" in dates_matched
|
||||
assert "2024-01-03" in dates_matched
|
||||
|
||||
|
||||
def test_bonus_only_match(pb):
|
||||
# No main number matches but bonus=7 matches draw 1
|
||||
results = check_ticket(pb["id"], [3, 4, 5, 6, 7], bonus=7)
|
||||
bonus_only = [r for r in results if r["draw_date"] == "2024-01-01"]
|
||||
assert len(bonus_only) == 1
|
||||
assert bonus_only[0]["main_matches"] == 0
|
||||
assert bonus_only[0]["bonus_match"] is True
|
||||
assert bonus_only[0]["prize_tier"] == "Bonus Only"
|
||||
|
||||
|
||||
def test_no_match_returns_empty(pb):
|
||||
# Numbers that don't appear in any draw
|
||||
results = check_ticket(pb["id"], [3, 4, 5, 6, 7])
|
||||
assert results == []
|
||||
|
||||
|
||||
def test_no_bonus_in_ticket(pb):
|
||||
results = check_ticket(pb["id"], [1, 13, 36, 61, 69], bonus=None)
|
||||
top = results[0]
|
||||
assert top["bonus_match"] is False
|
||||
assert top["prize_tier"] == "Match 5"
|
||||
|
||||
|
||||
def test_results_sorted_by_matches_desc(pb):
|
||||
# Mix of matches: use numbers that appear in multiple draws with varying counts
|
||||
results = check_ticket(pb["id"], [1, 13, 36, 61, 69])
|
||||
if len(results) > 1:
|
||||
for i in range(len(results) - 1):
|
||||
assert results[i]["main_matches"] >= results[i + 1]["main_matches"]
|
||||
|
||||
|
||||
def test_result_contains_required_keys(pb):
|
||||
results = check_ticket(pb["id"], [1, 13, 36, 61, 69])
|
||||
for key in ("draw_date", "draw_numbers", "draw_bonus",
|
||||
"main_matches", "bonus_match", "prize_tier"):
|
||||
assert key in results[0]
|
||||
|
||||
|
||||
def test_empty_db_returns_empty(tmp_db):
|
||||
pb = get_game_by_name("Powerball")
|
||||
assert check_ticket(pb["id"], [1, 2, 3, 4, 5]) == []
|
||||
|
||||
|
||||
# ── parse_numbers ─────────────────────────────────────────────────────────────
|
||||
|
||||
def test_parse_space_separated(tmp_db):
|
||||
assert parse_numbers("7 14 32 56 68") == [7, 14, 32, 56, 68]
|
||||
|
||||
def test_parse_comma_separated(tmp_db):
|
||||
assert parse_numbers("7,14,32,56,68") == [7, 14, 32, 56, 68]
|
||||
|
||||
def test_parse_mixed_delimiters(tmp_db):
|
||||
assert parse_numbers("7, 14, 32") == [7, 14, 32]
|
||||
|
||||
def test_parse_returns_sorted(tmp_db):
|
||||
assert parse_numbers("68 7 32") == [7, 32, 68]
|
||||
|
||||
def test_parse_empty_raises(tmp_db):
|
||||
with pytest.raises(ValueError, match="No numbers"):
|
||||
parse_numbers(" ")
|
||||
|
||||
def test_parse_non_digit_raises(tmp_db):
|
||||
with pytest.raises(ValueError):
|
||||
parse_numbers("7 14 abc")
|
||||
@@ -0,0 +1,192 @@
|
||||
"""
|
||||
tests/test_custom_games.py
|
||||
---------------------------
|
||||
Tests for add_game() and delete_game() in db/models.py,
|
||||
covering custom game management.
|
||||
"""
|
||||
|
||||
import pytest
|
||||
from db.models import (
|
||||
add_game, delete_game, get_all_games, get_game_by_name,
|
||||
get_game_by_id, insert_draw, _BUILTIN_GAMES,
|
||||
)
|
||||
|
||||
|
||||
# ── add_game ──────────────────────────────────────────────────────────────────
|
||||
|
||||
def test_add_game_creates_row(tmp_db):
|
||||
gid = add_game("EuroMillions", 5, 50, bonus_count=2, bonus_max=12)
|
||||
game = get_game_by_id(gid)
|
||||
assert game is not None
|
||||
assert game["name"] == "EuroMillions"
|
||||
|
||||
|
||||
def test_add_game_returns_id(tmp_db):
|
||||
gid = add_game("UK Lotto", 6, 59)
|
||||
assert isinstance(gid, int)
|
||||
assert gid > 0
|
||||
|
||||
|
||||
def test_add_game_default_active(tmp_db):
|
||||
gid = add_game("Test Game", 5, 45)
|
||||
game = get_game_by_id(gid)
|
||||
assert game["active"] == 1
|
||||
|
||||
|
||||
def test_add_game_appears_in_get_all_games(tmp_db):
|
||||
add_game("EuroMillions", 5, 50, bonus_count=2, bonus_max=12)
|
||||
names = [g["name"] for g in get_all_games()]
|
||||
assert "EuroMillions" in names
|
||||
|
||||
|
||||
def test_add_game_parameters_stored_correctly(tmp_db):
|
||||
gid = add_game("Custom", 6, 45, bonus_count=1, bonus_max=20)
|
||||
game = get_game_by_id(gid)
|
||||
assert game["main_count"] == 6
|
||||
assert game["main_max"] == 45
|
||||
assert game["bonus_count"] == 1
|
||||
assert game["bonus_max"] == 20
|
||||
|
||||
|
||||
def test_add_game_duplicate_name_raises(tmp_db):
|
||||
add_game("Test Game", 5, 45)
|
||||
with pytest.raises(ValueError, match="already exists"):
|
||||
add_game("Test Game", 6, 50)
|
||||
|
||||
|
||||
def test_add_game_blank_name_raises(tmp_db):
|
||||
with pytest.raises(ValueError, match="blank"):
|
||||
add_game(" ", 5, 45)
|
||||
|
||||
|
||||
def test_add_game_no_bonus(tmp_db):
|
||||
gid = add_game("No Bonus Game", 6, 49, bonus_count=0, bonus_max=0)
|
||||
game = get_game_by_id(gid)
|
||||
assert game["bonus_count"] == 0
|
||||
assert game["bonus_max"] == 0
|
||||
|
||||
|
||||
def test_add_game_builtin_name_raises(tmp_db):
|
||||
with pytest.raises(ValueError):
|
||||
add_game("Powerball", 5, 69, 1, 26)
|
||||
|
||||
|
||||
# ── delete_game ───────────────────────────────────────────────────────────────
|
||||
|
||||
def test_delete_custom_game_no_draws(tmp_db):
|
||||
gid = add_game("Temp Game", 5, 45)
|
||||
result = delete_game(gid)
|
||||
assert result is True
|
||||
assert get_game_by_id(gid) is None
|
||||
|
||||
|
||||
def test_delete_custom_game_removed_from_list(tmp_db):
|
||||
gid = add_game("Gone Game", 5, 45)
|
||||
delete_game(gid)
|
||||
names = [g["name"] for g in get_all_games()]
|
||||
assert "Gone Game" not in names
|
||||
|
||||
|
||||
def test_delete_game_with_draws_returns_false(tmp_db):
|
||||
gid = add_game("Active Game", 5, 45)
|
||||
insert_draw(gid, "2024-01-01", [1, 2, 3, 4, 5])
|
||||
result = delete_game(gid)
|
||||
assert result is False
|
||||
assert get_game_by_id(gid) is not None
|
||||
|
||||
|
||||
def test_delete_builtin_powerball_returns_false(tmp_db):
|
||||
pb = get_game_by_name("Powerball")
|
||||
result = delete_game(pb["id"])
|
||||
assert result is False
|
||||
assert get_game_by_name("Powerball") is not None
|
||||
|
||||
|
||||
def test_delete_builtin_mega_millions_returns_false(tmp_db):
|
||||
mm = get_game_by_name("Mega Millions")
|
||||
result = delete_game(mm["id"])
|
||||
assert result is False
|
||||
|
||||
|
||||
def test_delete_nonexistent_game_returns_false(tmp_db):
|
||||
result = delete_game(99999)
|
||||
assert result is False
|
||||
|
||||
|
||||
def test_delete_game_clears_predictions(tmp_db):
|
||||
from db.models import insert_prediction, get_predictions
|
||||
gid = add_game("Prediction Game", 5, 45)
|
||||
insert_prediction(gid, "Hot Numbers", [1, 2, 3, 4, 5])
|
||||
delete_game(gid)
|
||||
preds = get_predictions(game_id=gid, limit=100)
|
||||
assert preds == []
|
||||
|
||||
|
||||
# ── integration: custom game works with existing features ─────────────────────
|
||||
|
||||
def test_custom_game_works_with_frequency_analysis(tmp_db):
|
||||
from core.analyzer import frequency_analysis
|
||||
gid = add_game("My Lottery", 6, 49, bonus_count=1, bonus_max=10)
|
||||
insert_draw(gid, "2024-01-01", [1, 7, 14, 22, 35, 49])
|
||||
insert_draw(gid, "2024-01-03", [1, 7, 15, 23, 36, 48])
|
||||
freq = frequency_analysis(gid)
|
||||
assert freq[1] == 2
|
||||
assert freq[7] == 2
|
||||
|
||||
|
||||
def test_custom_game_works_with_predictor(tmp_db):
|
||||
from core.predictor import hot_numbers
|
||||
gid = add_game("My Lottery", 6, 49, bonus_count=1, bonus_max=10)
|
||||
insert_draw(gid, "2024-01-01", [1, 7, 14, 22, 35, 49], bonus=3)
|
||||
insert_draw(gid, "2024-01-03", [1, 7, 15, 23, 36, 48], bonus=5)
|
||||
result = hot_numbers(gid)
|
||||
assert len(result["numbers"]) == 6
|
||||
assert all(1 <= n <= 49 for n in result["numbers"])
|
||||
|
||||
|
||||
# ── UI smoke tests ────────────────────────────────────────────────────────────
|
||||
|
||||
def _has_display():
|
||||
try:
|
||||
import tkinter as tk
|
||||
r = tk.Tk(); r.withdraw(); r.destroy()
|
||||
return True
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
|
||||
@pytest.mark.skipif(not _has_display(), reason="no display available")
|
||||
def test_settings_shows_add_button(tmp_db):
|
||||
import tkinter as tk
|
||||
from ui.settings import SettingsScreen
|
||||
|
||||
try:
|
||||
root = tk.Tk()
|
||||
root.withdraw()
|
||||
except Exception:
|
||||
pytest.skip("Tkinter init failed")
|
||||
try:
|
||||
screen = SettingsScreen(root)
|
||||
screen.refresh()
|
||||
assert screen.winfo_exists()
|
||||
finally:
|
||||
root.destroy()
|
||||
|
||||
|
||||
@pytest.mark.skipif(not _has_display(), reason="no display available")
|
||||
def test_settings_custom_game_shows_delete_button(tmp_db):
|
||||
import tkinter as tk
|
||||
from ui.settings import SettingsScreen
|
||||
|
||||
add_game("My Custom Lottery", 5, 50)
|
||||
try:
|
||||
root = tk.Tk()
|
||||
root.withdraw()
|
||||
except Exception:
|
||||
pytest.skip("Tkinter init failed")
|
||||
try:
|
||||
screen = SettingsScreen(root)
|
||||
screen.refresh()
|
||||
assert screen.winfo_exists()
|
||||
finally:
|
||||
root.destroy()
|
||||
Reference in New Issue
Block a user