binárka / linux (pull_request) Successful in 11s
image / image (pull_request) Successful in 18s
testy / pytest (3.11) (pull_request) Successful in 1s
testy / pytest (3.12) (pull_request) Successful in 0s
testy / pytest (3.13) (pull_request) Successful in 0s
testy / pytest (3.14) (pull_request) Successful in 1s
Druhý krok po #16, rovnou celý. Dvojkolejnost dávala smysl pro projekt s neznámými uživateli; tady běží jedna instance a ta je přemigrovaná. Pryč je: - deploy/pve-kostky.sh (přesměrování na nový skript) - stěhování ze starého jména v instalátoru včetně bloku STARY_* - záložní jméno přílohy kostky-* v instalátoru - věšení příloh pod dvěma jmény v obou workflow - záloha na ~/.local/share/kostky/dice.db ve storage.py - odstavce o přechodu v README Jediné, co ve zdrojácích zůstalo, je docstring "Pravidla hry v kostky" v core.py — to je česky pojmenovaná hra, ne identifikátor. POZOR na pořadí: instalátor teď hledá jen dice-counter-*, a poslední vydání (v1.2.1) má na releasu pouze kostky-*. Do otagování v1.2.2 tedy `update` ani čerstvá instalace nemají co stáhnout. Merge a tag patří k sobě. Ověřeno v debianím kontejneru: čistá instalace, druhé spuštění za sebou, a vydání bez nových jmen spadne se srozumitelnou hláškou místo toho, aby sáhlo po starém. Binárka postavená lokálně zakládá databázi v …/dice-counter/ a starou vedle sebe ignoruje. 118 testů. Closes #19
252 lines
8.5 KiB
Python
252 lines
8.5 KiB
Python
import sqlite3
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
import storage
|
|
|
|
|
|
@pytest.fixture
|
|
def conn(tmp_path):
|
|
connection = storage.connect(tmp_path / "test.db")
|
|
yield connection
|
|
connection.close()
|
|
|
|
|
|
def test_stejne_jmeno_je_stejny_hrac(conn):
|
|
"""Arkádová identita: Adam z jedné hry je Adam i v další."""
|
|
first = storage.create_game(conn, ["Adam", "Eva"])
|
|
second = storage.create_game(conn, ["adam", "Petr"])
|
|
|
|
adam_first = [p["id"] for p in storage.seating(conn, first) if p["name"] == "Adam"]
|
|
adam_second = [p["id"] for p in storage.seating(conn, second)][0]
|
|
|
|
assert adam_first[0] == adam_second
|
|
|
|
|
|
def test_normalizace_jmen():
|
|
assert storage.normalize(" Adam ") == storage.normalize("adam")
|
|
assert storage.normalize("Jan Novák") == storage.normalize("jan novák")
|
|
assert storage.normalize("Adam") != storage.normalize("Ádám")
|
|
|
|
|
|
def test_prejmenovani_se_propise_do_starych_her(conn):
|
|
game_id = storage.create_game(conn, ["Adam", "Eva"])
|
|
adam = storage.seating(conn, game_id)[0]["id"]
|
|
|
|
storage.rename_player(conn, adam, "Adam Š.")
|
|
|
|
assert [p["name"] for p in storage.seating(conn, game_id)] == ["Adam Š.", "Eva"]
|
|
|
|
|
|
def test_slouceni_hracu_po_preklepu(conn):
|
|
good = storage.create_game(conn, ["Adam", "Eva"])
|
|
typo = storage.create_game(conn, ["Adm", "Eva"])
|
|
|
|
adam = storage.get_or_create_player(conn, "Adam")
|
|
adm = storage.get_or_create_player(conn, "Adm")
|
|
|
|
game = storage.load_game(conn, typo)
|
|
storage.add_turn(conn, typo, game.add_score(150).turn)
|
|
|
|
storage.merge_players(conn, adm, adam)
|
|
|
|
assert [p["name"] for p in storage.seating(conn, typo)] == ["Adam", "Eva"]
|
|
assert storage.load_game(conn, typo).totals()["Adam"] == 150
|
|
assert [p["id"] for p in storage.known_players(conn)].count(adm) == 0
|
|
assert storage.load_game(conn, good).totals() == {"Adam": 0, "Eva": 0}
|
|
|
|
|
|
def test_hra_se_nacte_zpatky_i_s_tahy(conn):
|
|
game_id = storage.create_game(conn, ["Adam", "Eva"], final_score=500)
|
|
game = storage.load_game(conn, game_id)
|
|
|
|
for value in (100, 50, 0, 30):
|
|
storage.add_turn(conn, game_id, game.add_score(value).turn)
|
|
|
|
loaded = storage.load_game(conn, game_id)
|
|
|
|
assert loaded.names == ["Adam", "Eva"]
|
|
assert loaded.final_score == 500
|
|
assert loaded.totals() == {"Adam": 100, "Eva": 80}
|
|
assert loaded.current_player == "Adam"
|
|
assert loaded.round_number == 3
|
|
|
|
|
|
def test_vynulovani_se_uklada_jako_nula_s_priznakem(conn):
|
|
game_id = storage.create_game(conn, ["Adam"], final_score=500)
|
|
game = storage.load_game(conn, game_id)
|
|
|
|
for value in (300, 0, 0, 0):
|
|
storage.add_turn(conn, game_id, game.add_score(value).turn)
|
|
|
|
rows = conn.execute("SELECT value, reset FROM turns ORDER BY id").fetchall()
|
|
|
|
assert [r["value"] for r in rows] == [300, 0, 0, 0]
|
|
assert [r["reset"] for r in rows] == [0, 0, 0, 1]
|
|
assert storage.load_game(conn, game_id).totals()["Adam"] == 0
|
|
|
|
|
|
def test_rozehrana_hra_se_najde_a_dokonci(conn):
|
|
game_id = storage.create_game(conn, ["Adam", "Eva"], final_score=100)
|
|
|
|
assert storage.running_game(conn) == game_id
|
|
|
|
game = storage.load_game(conn, game_id)
|
|
storage.add_turn(conn, game_id, game.add_score(100).turn)
|
|
result = game.add_score(10)
|
|
storage.add_turn(conn, game_id, result.turn)
|
|
storage.finish_game(conn, game_id, result.winner)
|
|
|
|
assert storage.running_game(conn) is None
|
|
assert storage.game_row(conn, game_id)["status"] == storage.STATUS_FINISHED
|
|
assert storage.game_row(conn, game_id)["winner_name"] == "Adam"
|
|
|
|
|
|
def test_undo_smaze_tah_a_vrati_hru_do_hry(conn):
|
|
game_id = storage.create_game(conn, ["Adam", "Eva"], final_score=100)
|
|
game = storage.load_game(conn, game_id)
|
|
storage.add_turn(conn, game_id, game.add_score(100).turn)
|
|
result = game.add_score(10)
|
|
storage.add_turn(conn, game_id, result.turn)
|
|
storage.finish_game(conn, game_id, result.winner)
|
|
|
|
assert storage.undo_turn(conn, game_id)
|
|
|
|
row = storage.game_row(conn, game_id)
|
|
assert row["status"] == storage.STATUS_RUNNING
|
|
assert row["winner_id"] is None
|
|
assert storage.load_game(conn, game_id).current_player == "Eva"
|
|
|
|
|
|
def test_opustena_hra(conn):
|
|
game_id = storage.create_game(conn, ["Adam", "Eva"])
|
|
storage.abandon_game(conn, game_id)
|
|
|
|
assert storage.running_game(conn) is None
|
|
assert storage.game_row(conn, game_id)["status"] == storage.STATUS_ABANDONED
|
|
|
|
|
|
def test_hrac_nesmi_sedet_u_stolu_dvakrat(conn):
|
|
with pytest.raises(ValueError):
|
|
storage.create_game(conn, ["Adam", "adam"])
|
|
|
|
|
|
def test_prazdne_jmeno(conn):
|
|
with pytest.raises(ValueError):
|
|
storage.get_or_create_player(conn, " ")
|
|
|
|
|
|
def test_umisteni_databaze_ze_zdrojaku(monkeypatch):
|
|
"""Ze zdrojáků se ukládá do pracovního adresáře."""
|
|
monkeypatch.delenv("DICE_DB", raising=False)
|
|
monkeypatch.delattr(storage.sys, "frozen", raising=False)
|
|
|
|
assert storage.default_db() == Path("dice.db")
|
|
|
|
|
|
def test_umisteni_databaze_z_binarky(monkeypatch, tmp_path):
|
|
"""Binárka se rozbaluje do dočasného adresáře, databáze tam nesmí."""
|
|
monkeypatch.delenv("DICE_DB", raising=False)
|
|
monkeypatch.setattr(storage.sys, "frozen", True, raising=False)
|
|
monkeypatch.setattr(storage.sys, "platform", "linux")
|
|
monkeypatch.setenv("XDG_DATA_HOME", str(tmp_path))
|
|
|
|
assert storage.default_db() == tmp_path / "dice-counter" / "dice.db"
|
|
|
|
|
|
def test_data_home_podle_systemu(monkeypatch, tmp_path):
|
|
"""Každý systém má svůj adresář na data; XDG je jen ten linuxový."""
|
|
monkeypatch.setenv("LOCALAPPDATA", str(tmp_path / "AppData" / "Local"))
|
|
monkeypatch.setattr(Path, "home", classmethod(lambda cls: tmp_path))
|
|
|
|
monkeypatch.setattr(storage.sys, "platform", "win32")
|
|
assert storage.data_home() == tmp_path / "AppData" / "Local"
|
|
|
|
monkeypatch.setattr(storage.sys, "platform", "darwin")
|
|
assert storage.data_home() == tmp_path / "Library" / "Application Support"
|
|
|
|
monkeypatch.setattr(storage.sys, "platform", "linux")
|
|
monkeypatch.delenv("XDG_DATA_HOME", raising=False)
|
|
assert storage.data_home() == tmp_path / ".local" / "share"
|
|
|
|
|
|
def test_dice_db_prebije_oboje(monkeypatch, tmp_path):
|
|
monkeypatch.setattr(storage.sys, "frozen", True, raising=False)
|
|
monkeypatch.setenv("DICE_DB", str(tmp_path / "jinde.db"))
|
|
|
|
assert storage.default_db() == tmp_path / "jinde.db"
|
|
|
|
|
|
def test_connect_zalozi_chybejici_adresar(tmp_path):
|
|
conn = storage.connect(tmp_path / "novy" / "adresar" / "dice.db")
|
|
conn.close()
|
|
|
|
assert (tmp_path / "novy" / "adresar" / "dice.db").exists()
|
|
|
|
|
|
# --- verze schématu ---------------------------------------------------------
|
|
|
|
def verze(conn) -> int:
|
|
return conn.execute("PRAGMA user_version").fetchone()[0]
|
|
|
|
|
|
def test_nova_databaze_je_orazitkovana(tmp_path):
|
|
conn = storage.connect(tmp_path / "nova.db")
|
|
try:
|
|
assert verze(conn) == storage.SCHEMA_VERSION
|
|
finally:
|
|
conn.close()
|
|
|
|
|
|
def test_databaze_z_doby_pred_verzovanim_prezije(tmp_path):
|
|
"""Přesně to, co lidem leží na disku z verzí 1.0 a 1.1.
|
|
|
|
Tabulky má, razítko ne. Nesmí se ani přijít o data, ani spadnout.
|
|
"""
|
|
path = tmp_path / "stara.db"
|
|
stara = sqlite3.connect(path)
|
|
stara.executescript(storage.SCHEMA) # bez PRAGMA user_version
|
|
stara.execute("INSERT INTO players (name, key, created_at) "
|
|
"VALUES ('Adam', 'adam', '2026-01-01T00:00:00')")
|
|
stara.commit()
|
|
stara.close()
|
|
|
|
conn = storage.connect(path)
|
|
try:
|
|
assert verze(conn) == storage.SCHEMA_VERSION
|
|
assert [p["name"] for p in storage.known_players(conn)] == ["Adam"]
|
|
finally:
|
|
conn.close()
|
|
|
|
|
|
def test_migrace_dojede_jen_ty_chybejici(tmp_path, monkeypatch):
|
|
"""Podruhé už krok proběhnout nesmí — jinak by ALTER TABLE spadl."""
|
|
monkeypatch.setattr(storage, "MIGRATIONS",
|
|
["ALTER TABLE players ADD COLUMN barva TEXT;"])
|
|
monkeypatch.setattr(storage, "SCHEMA_VERSION", 2)
|
|
|
|
path = tmp_path / "migrovana.db"
|
|
conn = storage.connect(path)
|
|
conn.close()
|
|
|
|
conn = storage.connect(path) # druhé spuštění téže verze
|
|
try:
|
|
assert verze(conn) == 2
|
|
sloupce = [r[1] for r in conn.execute("PRAGMA table_info(players)")]
|
|
assert "barva" in sloupce
|
|
finally:
|
|
conn.close()
|
|
|
|
|
|
def test_databaze_z_novejsi_verze_se_odmitne(tmp_path):
|
|
"""Radši srozumitelná hláška než tiše rozbitá data."""
|
|
path = tmp_path / "budouci.db"
|
|
conn = storage.connect(path)
|
|
conn.execute(f"PRAGMA user_version = {storage.SCHEMA_VERSION + 1}")
|
|
conn.commit()
|
|
conn.close()
|
|
|
|
with pytest.raises(RuntimeError, match="novější verze"):
|
|
storage.connect(path)
|