mirror of
https://github.com/rr-/pq-cli.git
synced 2026-11-12 22:40:36 -07:00
Drive the line-based UI in-process: input() answers come from a list and the output is captured. The tests walk the main menu through creating, playing, querying, deleting, importing and exporting characters, plus bad menu input and the Ctrl+C handler. The play loop runs on a fake clock that advances a minute per call and stops when time.sleep has been called a set number of times, so it levels up and saves on schedule without waiting. The test helpers move to tests/helpers.py, which now also holds the input() feeder.
132 lines
3.3 KiB
Python
132 lines
3.3 KiB
Python
import pickle
|
|
from datetime import datetime, timedelta
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
from helpers import hero
|
|
|
|
from pqcli import roster as roster_module
|
|
from pqcli.roster import Roster
|
|
|
|
START = datetime(2024, 1, 1, 12, 0, 0)
|
|
|
|
|
|
class Clock:
|
|
"""Stands in for datetime in pqcli.roster; only now() is used."""
|
|
|
|
current = START
|
|
|
|
@classmethod
|
|
def now(cls) -> datetime:
|
|
return cls.current
|
|
|
|
@classmethod
|
|
def advance(cls, seconds: float) -> None:
|
|
cls.current += timedelta(seconds=seconds)
|
|
|
|
|
|
@pytest.fixture
|
|
def clock(monkeypatch: pytest.MonkeyPatch) -> type[Clock]:
|
|
monkeypatch.setattr(Clock, "current", START)
|
|
monkeypatch.setattr(roster_module, "datetime", Clock)
|
|
return Clock
|
|
|
|
|
|
def names(path: Path) -> list[str]:
|
|
return [player.name for player in pickle.loads(path.read_bytes())]
|
|
|
|
|
|
def test_load_missing_file_gives_empty_roster(tmp_path: Path) -> None:
|
|
path = tmp_path / "nowhere" / "save.dat"
|
|
roster = Roster.load(path)
|
|
assert roster.players == []
|
|
assert roster.path == path
|
|
assert not path.parent.exists()
|
|
|
|
|
|
def test_load_accepts_a_string_path(tmp_path: Path) -> None:
|
|
path = tmp_path / "save.dat"
|
|
Roster(path, [hero("Ann")]).save()
|
|
assert Roster.load(str(path)).path == path
|
|
|
|
|
|
def test_save_then_load_round_trip(tmp_path: Path) -> None:
|
|
path = tmp_path / "deep" / "dir" / "save.dat"
|
|
player = hero("Ann", ticks=1000)
|
|
Roster(path, [player, hero("Bob")]).save()
|
|
|
|
loaded = Roster.load(path).players
|
|
assert [p.name for p in loaded] == ["Ann", "Bob"]
|
|
assert loaded[0].level == player.level
|
|
assert loaded[0].inventory.gold == player.inventory.gold
|
|
assert loaded[0].quest_book.quests == player.quest_book.quests
|
|
|
|
|
|
def test_save_keeps_the_previous_save_as_old(tmp_path: Path) -> None:
|
|
path = tmp_path / "save.dat"
|
|
old = tmp_path / "save.dat.old"
|
|
roster = Roster(path, [hero("Ann")])
|
|
|
|
roster.save()
|
|
assert names(path) == ["Ann"]
|
|
assert not old.exists()
|
|
|
|
roster.players.append(hero("Bob"))
|
|
roster.save()
|
|
assert names(path) == ["Ann", "Bob"]
|
|
assert names(old) == ["Ann"]
|
|
|
|
roster.players.pop(0)
|
|
roster.save()
|
|
assert names(path) == ["Bob"]
|
|
assert names(old) == ["Ann", "Bob"]
|
|
|
|
assert sorted(p.name for p in tmp_path.iterdir()) == [
|
|
"save.dat",
|
|
"save.dat.old",
|
|
]
|
|
|
|
|
|
def test_save_periodically_waits_five_minutes(
|
|
tmp_path: Path, clock: type[Clock]
|
|
) -> None:
|
|
path = tmp_path / "save.dat"
|
|
roster = Roster(path, [hero("Ann")])
|
|
|
|
clock.advance(299)
|
|
roster.save_periodically()
|
|
assert not path.exists()
|
|
|
|
clock.advance(1)
|
|
roster.save_periodically()
|
|
assert names(path) == ["Ann"]
|
|
|
|
# the timer starts over after a save
|
|
roster.players.append(hero("Bob"))
|
|
clock.advance(299)
|
|
roster.save_periodically()
|
|
assert names(path) == ["Ann"]
|
|
|
|
clock.advance(1)
|
|
roster.save_periodically()
|
|
assert names(path) == ["Ann", "Bob"]
|
|
|
|
|
|
def test_manual_save_restarts_the_periodic_timer(
|
|
tmp_path: Path, clock: type[Clock]
|
|
) -> None:
|
|
path = tmp_path / "save.dat"
|
|
roster = Roster(path, [hero("Ann")])
|
|
|
|
clock.advance(200)
|
|
roster.save()
|
|
roster.players.append(hero("Bob"))
|
|
|
|
clock.advance(200)
|
|
roster.save_periodically()
|
|
assert names(path) == ["Ann"]
|
|
|
|
clock.advance(100)
|
|
roster.save_periodically()
|
|
assert names(path) == ["Ann", "Bob"]
|