05/23 Phase 7

This commit is contained in:
2026-05-23 11:58:51 -04:00
parent c1405b5fb8
commit 8fc1878670
8 changed files with 638 additions and 15 deletions
+287
View File
@@ -0,0 +1,287 @@
"""
tests/test_exporter.py
-----------------------
Tests for core/exporter.py — draw history, predictions, frequency analysis exports.
All tests write to tmp paths; no file-dialog interaction required.
"""
import csv
import os
import openpyxl
import pytest
from db.models import get_game_by_name, insert_draw, insert_prediction
from core.exporter import (
export_draws_excel,
export_draws_csv,
export_predictions_excel,
export_predictions_csv,
export_frequency_excel,
create_icon_png,
ensure_exports_dir,
default_path,
EXPORTS_DIR,
)
# ── Fixtures ──────────────────────────────────────────────────────────────────
@pytest.fixture()
def pb(tmp_db):
return get_game_by_name("Powerball")
@pytest.fixture()
def mm(tmp_db):
return get_game_by_name("Mega Millions")
@pytest.fixture()
def populated_db(pb, mm):
insert_draw(pb["id"], "2024-01-01", [1, 13, 36, 61, 69], bonus=7, multiplier="2x")
insert_draw(pb["id"], "2024-01-03", [5, 10, 20, 30, 40], bonus=15, multiplier="3x")
insert_draw(mm["id"], "2024-01-02", [2, 14, 37, 62, 70], bonus=5)
insert_prediction(pb["id"], "Hot Numbers", [1, 13, 36, 61, 69], bonus=7)
insert_prediction(mm["id"], "Due Numbers", [2, 14, 37, 62, 70], bonus=5)
return pb, mm
# ── Draw Excel ────────────────────────────────────────────────────────────────
def test_draws_excel_creates_file(tmp_path, populated_db):
fp = str(tmp_path / "draws.xlsx")
result = export_draws_excel(fp)
assert result == fp
assert os.path.exists(fp)
def test_draws_excel_header(tmp_path, populated_db):
fp = str(tmp_path / "draws.xlsx")
export_draws_excel(fp)
wb = openpyxl.load_workbook(fp)
ws = wb.active
headers = [ws.cell(1, c).value for c in range(1, 7)]
assert headers == ["Game", "Date", "Numbers", "Bonus", "Multiplier", "Source"]
def test_draws_excel_row_count(tmp_path, populated_db):
pb, mm = populated_db
fp = str(tmp_path / "draws.xlsx")
export_draws_excel(fp)
wb = openpyxl.load_workbook(fp)
ws = wb.active
# 3 draws inserted (header is row 1)
assert ws.max_row == 4
def test_draws_excel_game_filter(tmp_path, populated_db):
pb, mm = populated_db
fp = str(tmp_path / "pb_only.xlsx")
export_draws_excel(fp, game_id=pb["id"])
wb = openpyxl.load_workbook(fp)
ws = wb.active
games_in_file = {ws.cell(r, 1).value for r in range(2, ws.max_row + 1)}
assert games_in_file == {"Powerball"}
def test_draws_excel_date_filter(tmp_path, populated_db):
fp = str(tmp_path / "filtered.xlsx")
export_draws_excel(fp, date_from="2024-01-03", date_to="2024-01-03")
wb = openpyxl.load_workbook(fp)
ws = wb.active
# Only the 2024-01-03 draw
assert ws.max_row == 2
def test_draws_excel_empty_db(tmp_path, tmp_db):
fp = str(tmp_path / "empty.xlsx")
export_draws_excel(fp)
wb = openpyxl.load_workbook(fp)
ws = wb.active
assert ws.max_row == 1 # header only
# ── Draw CSV ──────────────────────────────────────────────────────────────────
def test_draws_csv_creates_file(tmp_path, populated_db):
fp = str(tmp_path / "draws.csv")
result = export_draws_csv(fp)
assert result == fp
assert os.path.exists(fp)
def test_draws_csv_header(tmp_path, populated_db):
fp = str(tmp_path / "draws.csv")
export_draws_csv(fp)
with open(fp, newline="", encoding="utf-8") as f:
reader = csv.reader(f)
header = next(reader)
assert header == ["Game", "Date", "Numbers", "Bonus", "Multiplier", "Source"]
def test_draws_csv_row_count(tmp_path, populated_db):
fp = str(tmp_path / "draws.csv")
export_draws_csv(fp)
with open(fp, newline="", encoding="utf-8") as f:
rows = list(csv.reader(f))
assert len(rows) == 4 # 1 header + 3 draws
def test_draws_csv_game_filter(tmp_path, populated_db):
pb, _ = populated_db
fp = str(tmp_path / "pb.csv")
export_draws_csv(fp, game_id=pb["id"])
with open(fp, newline="", encoding="utf-8") as f:
rows = list(csv.reader(f))
data_rows = rows[1:]
assert all(r[0] == "Powerball" for r in data_rows)
# ── Predictions Excel ─────────────────────────────────────────────────────────
def test_predictions_excel_creates_file(tmp_path, populated_db):
fp = str(tmp_path / "preds.xlsx")
result = export_predictions_excel(fp)
assert result == fp
assert os.path.exists(fp)
def test_predictions_excel_header(tmp_path, populated_db):
fp = str(tmp_path / "preds.xlsx")
export_predictions_excel(fp)
wb = openpyxl.load_workbook(fp)
ws = wb.active
headers = [ws.cell(1, c).value for c in range(1, 7)]
assert headers == ["ID", "Game", "Strategy", "Numbers", "Bonus", "Created"]
def test_predictions_excel_row_count(tmp_path, populated_db):
fp = str(tmp_path / "preds.xlsx")
export_predictions_excel(fp)
wb = openpyxl.load_workbook(fp)
ws = wb.active
assert ws.max_row == 3 # 1 header + 2 predictions
def test_predictions_excel_game_filter(tmp_path, populated_db):
pb, _ = populated_db
fp = str(tmp_path / "pb_preds.xlsx")
export_predictions_excel(fp, game_id=pb["id"])
wb = openpyxl.load_workbook(fp)
ws = wb.active
games = {ws.cell(r, 2).value for r in range(2, ws.max_row + 1)}
assert games == {"Powerball"}
# ── Predictions CSV ───────────────────────────────────────────────────────────
def test_predictions_csv_creates_file(tmp_path, populated_db):
fp = str(tmp_path / "preds.csv")
result = export_predictions_csv(fp)
assert result == fp
assert os.path.exists(fp)
def test_predictions_csv_header(tmp_path, populated_db):
fp = str(tmp_path / "preds.csv")
export_predictions_csv(fp)
with open(fp, newline="", encoding="utf-8") as f:
header = next(csv.reader(f))
assert header == ["ID", "Game", "Strategy", "Numbers", "Bonus", "Created"]
def test_predictions_csv_row_count(tmp_path, populated_db):
fp = str(tmp_path / "preds.csv")
export_predictions_csv(fp)
with open(fp, newline="", encoding="utf-8") as f:
rows = list(csv.reader(f))
assert len(rows) == 3 # 1 header + 2 predictions
# ── Frequency Excel ───────────────────────────────────────────────────────────
def test_frequency_excel_creates_file(tmp_path, populated_db):
pb, _ = populated_db
fp = str(tmp_path / "freq.xlsx")
result = export_frequency_excel(fp, pb["id"])
assert result == fp
assert os.path.exists(fp)
def test_frequency_excel_header(tmp_path, populated_db):
pb, _ = populated_db
fp = str(tmp_path / "freq.xlsx")
export_frequency_excel(fp, pb["id"])
wb = openpyxl.load_workbook(fp)
ws = wb.active
headers = [ws.cell(1, c).value for c in range(1, 4)]
assert headers == ["Number", "Frequency", "Gap (draws since last seen)"]
def test_frequency_excel_covers_full_pool(tmp_path, populated_db):
pb, _ = populated_db
fp = str(tmp_path / "freq.xlsx")
export_frequency_excel(fp, pb["id"])
wb = openpyxl.load_workbook(fp)
ws = wb.active
# Powerball main_max=69: 69 data rows + 1 header = 70
assert ws.max_row == 70
def test_frequency_excel_number_column(tmp_path, populated_db):
pb, _ = populated_db
fp = str(tmp_path / "freq.xlsx")
export_frequency_excel(fp, pb["id"])
wb = openpyxl.load_workbook(fp)
ws = wb.active
numbers = [ws.cell(r, 1).value for r in range(2, ws.max_row + 1)]
assert numbers == list(range(1, 70))
# ── Icon PNG ──────────────────────────────────────────────────────────────────
def test_create_icon_png_creates_file(tmp_path):
path = str(tmp_path / "icon.png")
create_icon_png(path)
assert os.path.exists(path)
def test_create_icon_png_is_valid_png(tmp_path):
path = str(tmp_path / "icon.png")
create_icon_png(path)
with open(path, "rb") as f:
sig = f.read(8)
assert sig == b"\x89PNG\r\n\x1a\n"
def test_create_icon_png_default_size(tmp_path):
path = str(tmp_path / "icon.png")
create_icon_png(path)
size = os.path.getsize(path)
assert size > 100 # a valid non-empty PNG
def test_create_icon_png_custom_size(tmp_path):
path = str(tmp_path / "icon32.png")
create_icon_png(path, size=32)
assert os.path.exists(path)
# ── Helpers ───────────────────────────────────────────────────────────────────
def test_ensure_exports_dir_creates_directory(tmp_path, monkeypatch):
target = str(tmp_path / "new_exports")
monkeypatch.setattr("core.exporter.EXPORTS_DIR", target)
import core.exporter as exp
exp.EXPORTS_DIR = target
exp.ensure_exports_dir()
assert os.path.isdir(target)
def test_default_path_returns_string_with_stem(tmp_path, monkeypatch):
import core.exporter as exp
monkeypatch.setattr(exp, "EXPORTS_DIR", str(tmp_path))
path = exp.default_path("draws", "xlsx")
assert "draws" in os.path.basename(path)
assert path.endswith(".xlsx")