""" 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")