05/23 Phase 18,19
This commit is contained in:
@@ -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
|
||||
@@ -0,0 +1,228 @@
|
||||
"""
|
||||
tests/test_importer.py
|
||||
----------------------
|
||||
Tests for core/importer.py — CSV draw import.
|
||||
|
||||
Powerball config: 5 main from 1–69, 1 bonus from 1–26.
|
||||
"""
|
||||
|
||||
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"
|
||||
Reference in New Issue
Block a user