05/23 Phase 18,19

This commit is contained in:
2026-05-23 16:31:55 -04:00
parent 7506e94d61
commit 2553406254
9 changed files with 674 additions and 5 deletions
+104
View File
@@ -0,0 +1,104 @@
"""
tests/test_backup.py
---------------------
Tests for db.database backup_db() and restore_db().
"""
import os
import pytest
from db.database import backup_db, restore_db, DB_PATH, get_connection
from db.models import insert_draw, get_draws_with_game, get_game_by_name
# ── backup_db ─────────────────────────────────────────────────────────────────
def test_backup_creates_file(tmp_db, tmp_path):
dest = backup_db(dest_dir=str(tmp_path))
assert os.path.isfile(dest)
def test_backup_filename_contains_timestamp(tmp_db, tmp_path):
dest = backup_db(dest_dir=str(tmp_path))
basename = os.path.basename(dest)
assert basename.startswith("lottosight_backup_")
assert basename.endswith(".db")
def test_backup_returns_path_string(tmp_db, tmp_path):
result = backup_db(dest_dir=str(tmp_path))
assert isinstance(result, str)
assert len(result) > 0
def test_backup_file_is_valid_sqlite(tmp_db, tmp_path):
dest = backup_db(dest_dir=str(tmp_path))
import sqlite3
conn = sqlite3.connect(dest)
cursor = conn.cursor()
cursor.execute("SELECT name FROM sqlite_master WHERE type='table'")
tables = {r[0] for r in cursor.fetchall()}
conn.close()
assert "games" in tables
assert "draws" in tables
def test_backup_preserves_draw_data(tmp_db, tmp_path):
game = get_game_by_name("Powerball")
insert_draw(game["id"], "2024-06-01", [5, 14, 22, 36, 69], bonus=7)
dest = backup_db(dest_dir=str(tmp_path))
import sqlite3
conn = sqlite3.connect(dest)
conn.row_factory = sqlite3.Row
cursor = conn.cursor()
cursor.execute("SELECT numbers FROM draws WHERE draw_date='2024-06-01'")
row = cursor.fetchone()
conn.close()
assert row is not None
assert "5" in row["numbers"]
def test_backup_creates_dest_dir_if_missing(tmp_db, tmp_path):
nested = str(tmp_path / "a" / "b" / "c")
dest = backup_db(dest_dir=nested)
assert os.path.isfile(dest)
def test_backup_default_dest_dir(tmp_db, monkeypatch, tmp_path):
from core.paths import user_data_dir as _udd
monkeypatch.setattr("db.database.user_data_dir", lambda: str(tmp_path))
dest = backup_db() # no dest_dir — should use exports/ sub-dir
assert os.path.isfile(dest)
assert "exports" in dest
# ── restore_db ────────────────────────────────────────────────────────────────
def test_restore_missing_file_raises(tmp_db):
with pytest.raises(FileNotFoundError):
restore_db("/nonexistent/backup.db")
def test_restore_replaces_live_db(tmp_db, tmp_path):
game = get_game_by_name("Powerball")
insert_draw(game["id"], "2024-07-04", [3, 17, 28, 45, 62], bonus=12)
backup_path = backup_db(dest_dir=str(tmp_path))
# Delete the draw from the live DB
conn = get_connection()
conn.execute("DELETE FROM draws WHERE draw_date='2024-07-04'")
conn.commit()
conn.close()
draws_before = get_draws_with_game(game_id=game["id"])
assert not any(d["draw_date"] == "2024-07-04" for d in draws_before)
restore_db(backup_path)
draws_after = get_draws_with_game(game_id=game["id"])
assert any(d["draw_date"] == "2024-07-04" for d in draws_after)
def test_restore_returns_none(tmp_db, tmp_path):
backup_path = backup_db(dest_dir=str(tmp_path))
result = restore_db(backup_path)
assert result is None
+228
View File
@@ -0,0 +1,228 @@
"""
tests/test_importer.py
----------------------
Tests for core/importer.py — CSV draw import.
Powerball config: 5 main from 169, 1 bonus from 126.
"""
import os
import pytest
from db.models import get_game_by_name, add_game, get_draws_with_game
from core.importer import import_draws_csv, _parse_date
# ── _parse_date ───────────────────────────────────────────────────────────────
def test_parse_date_iso():
assert _parse_date("2024-01-15") == "2024-01-15"
def test_parse_date_us_slash():
assert _parse_date("01/15/2024") == "2024-01-15"
def test_parse_date_us_dash():
assert _parse_date("01-15-2024") == "2024-01-15"
def test_parse_date_invalid():
assert _parse_date("not-a-date") is None
def test_parse_date_header_text():
assert _parse_date("Date") is None
assert _parse_date("draw_date") is None
# ── Wide format (Date, N1, N2, ...) ──────────────────────────────────────────
def test_wide_no_header(tmp_db, tmp_path):
game = get_game_by_name("Powerball")
csv_file = tmp_path / "draws.csv"
csv_file.write_text(
"2024-01-01,1,13,36,61,69,7\n"
"2024-01-03,2,7,22,45,61,15\n"
)
result = import_draws_csv(game["id"], str(csv_file))
assert result["added"] == 2
assert result["skipped"] == 0
assert result["errors"] == []
def test_wide_with_header(tmp_db, tmp_path):
game = get_game_by_name("Powerball")
csv_file = tmp_path / "draws.csv"
csv_file.write_text(
"Date,Ball1,Ball2,Ball3,Ball4,Ball5,Bonus\n"
"2024-01-01,1,13,36,61,69,7\n"
"2024-01-03,2,7,22,45,61,15\n"
)
result = import_draws_csv(game["id"], str(csv_file))
assert result["added"] == 2
assert result["errors"] == []
def test_wide_no_bonus_column(tmp_db, tmp_path):
game = get_game_by_name("Powerball")
csv_file = tmp_path / "draws.csv"
csv_file.write_text("2024-01-01,1,13,36,61,69\n")
result = import_draws_csv(game["id"], str(csv_file))
assert result["added"] == 1
draws = get_draws_with_game(game_id=game["id"])
assert draws[0]["bonus"] is None
def test_wide_us_date_format(tmp_db, tmp_path):
game = get_game_by_name("Powerball")
csv_file = tmp_path / "draws.csv"
csv_file.write_text("01/15/2024,5,14,22,36,69,7\n")
result = import_draws_csv(game["id"], str(csv_file))
assert result["added"] == 1
draws = get_draws_with_game(game_id=game["id"])
assert draws[0]["draw_date"] == "2024-01-15"
# ── Packed format (Date, "N1,N2,...", Bonus) ──────────────────────────────────
def test_packed_format(tmp_db, tmp_path):
game = get_game_by_name("Powerball")
csv_file = tmp_path / "draws.csv"
csv_file.write_text('2024-01-01,"1,13,36,61,69",7\n')
result = import_draws_csv(game["id"], str(csv_file))
assert result["added"] == 1
assert result["errors"] == []
def test_packed_with_header(tmp_db, tmp_path):
game = get_game_by_name("Powerball")
csv_file = tmp_path / "draws.csv"
csv_file.write_text(
'Date,Numbers,Bonus\n'
'2024-01-01,"1,13,36,61,69",7\n'
'2024-01-03,"2,7,22,45,61",15\n'
)
result = import_draws_csv(game["id"], str(csv_file))
assert result["added"] == 2
# ── LottoSight export format ──────────────────────────────────────────────────
def test_lottosight_export_format(tmp_db, tmp_path):
game = get_game_by_name("Powerball")
csv_file = tmp_path / "export.csv"
csv_file.write_text(
"Game,Date,Numbers,Bonus,Multiplier,Source\n"
'Powerball,2024-01-01,"1,13,36,61,69",7,2x,powerball_ny\n'
'Powerball,2024-01-03,"2,7,22,45,61",15,3x,powerball_ny\n'
)
result = import_draws_csv(game["id"], str(csv_file))
assert result["added"] == 2
assert result["errors"] == []
# ── Duplicate detection ───────────────────────────────────────────────────────
def test_duplicate_skipped(tmp_db, tmp_path):
game = get_game_by_name("Powerball")
csv_file = tmp_path / "draws.csv"
csv_file.write_text(
"2024-01-01,1,13,36,61,69,7\n"
"2024-01-01,1,13,36,61,69,7\n" # same date → duplicate
)
result = import_draws_csv(game["id"], str(csv_file))
assert result["added"] == 1
assert result["skipped"] == 1
def test_reimport_all_skipped(tmp_db, tmp_path):
game = get_game_by_name("Powerball")
csv_file = tmp_path / "draws.csv"
csv_file.write_text("2024-01-01,1,13,36,61,69,7\n")
import_draws_csv(game["id"], str(csv_file))
result = import_draws_csv(game["id"], str(csv_file))
assert result["added"] == 0
assert result["skipped"] == 1
# ── Validation errors ─────────────────────────────────────────────────────────
def test_wrong_number_count(tmp_db, tmp_path):
game = get_game_by_name("Powerball")
csv_file = tmp_path / "draws.csv"
csv_file.write_text("2024-01-01,1,13,36,61\n") # only 4 numbers
result = import_draws_csv(game["id"], str(csv_file))
assert result["added"] == 0
assert len(result["errors"]) == 1 # rejected — too few numbers to parse
def test_number_out_of_range(tmp_db, tmp_path):
game = get_game_by_name("Powerball")
csv_file = tmp_path / "draws.csv"
csv_file.write_text("2024-01-01,1,13,36,61,99,7\n") # 99 > 69
result = import_draws_csv(game["id"], str(csv_file))
assert result["added"] == 0
assert len(result["errors"]) == 1
def test_mixed_valid_invalid(tmp_db, tmp_path):
game = get_game_by_name("Powerball")
csv_file = tmp_path / "draws.csv"
csv_file.write_text(
"2024-01-01,1,13,36,61,69,7\n"
"bad_date,1,2,3,4,5,6\n" # unparseable
"2024-01-03,2,7,22,45,61,15\n"
)
result = import_draws_csv(game["id"], str(csv_file))
assert result["added"] == 2
assert len(result["errors"]) == 1
# ── Edge cases ────────────────────────────────────────────────────────────────
def test_file_not_found(tmp_db):
game = get_game_by_name("Powerball")
result = import_draws_csv(game["id"], "/nonexistent/path/file.csv")
assert result["added"] == 0
assert len(result["errors"]) == 1
assert "not found" in result["errors"][0].lower()
def test_empty_file(tmp_db, tmp_path):
game = get_game_by_name("Powerball")
csv_file = tmp_path / "empty.csv"
csv_file.write_text("")
result = import_draws_csv(game["id"], str(csv_file))
assert result["added"] == 0
assert result["errors"] == ["File is empty"]
def test_blank_lines_ignored(tmp_db, tmp_path):
game = get_game_by_name("Powerball")
csv_file = tmp_path / "draws.csv"
csv_file.write_text(
"2024-01-01,1,13,36,61,69,7\n"
"\n"
"2024-01-03,2,7,22,45,61,15\n"
"\n"
)
result = import_draws_csv(game["id"], str(csv_file))
assert result["added"] == 2
assert result["errors"] == []
def test_unknown_game_id(tmp_db, tmp_path):
csv_file = tmp_path / "draws.csv"
csv_file.write_text("2024-01-01,1,2,3,4,5\n")
result = import_draws_csv(99999, str(csv_file))
assert result["added"] == 0
assert len(result["errors"]) == 1
def test_custom_game_import(tmp_db, tmp_path):
gid = add_game("My Lotto", 6, 49, bonus_count=0, bonus_max=0)
csv_file = tmp_path / "draws.csv"
csv_file.write_text(
"2024-01-01,5,14,22,33,41,48\n"
"2024-01-08,3,17,28,35,44,49\n"
)
result = import_draws_csv(gid, str(csv_file))
assert result["added"] == 2
assert result["errors"] == []
def test_data_persisted_correctly(tmp_db, tmp_path):
game = get_game_by_name("Powerball")
csv_file = tmp_path / "draws.csv"
csv_file.write_text("2024-03-15,5,14,22,36,69,7\n")
import_draws_csv(game["id"], str(csv_file))
draws = get_draws_with_game(game_id=game["id"])
assert len(draws) == 1
assert draws[0]["draw_date"] == "2024-03-15"
nums = [int(n) for n in draws[0]["numbers"].split(",")]
assert sorted(nums) == [5, 14, 22, 36, 69]
assert str(draws[0]["bonus"]) == "7"