mirror of
https://github.com/kuhyx/steam-backlog-enforcer.git
synced 2026-11-11 20:13:49 +01:00
Tests are now linted by the 10.00 gate (ignore-paths dropped), with no per-test disables. CtlServer/DaemonControl members the tests drive are public (renamed, behaviour unchanged); SteamAPIClient._get takes its params explicitly, as every caller already did. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
154 lines
5.6 KiB
Python
154 lines
5.6 KiB
Python
"""Tests for the main CLI: store, reset, setup, exception and block-gaming commands."""
|
|
|
|
from datetime import UTC, datetime
|
|
from unittest.mock import MagicMock, patch
|
|
|
|
import pytest
|
|
|
|
from steam_backlog_enforcer._store_window import DEFAULT_WINDOW_MINUTES
|
|
from steam_backlog_enforcer.config import Config, State
|
|
from steam_backlog_enforcer.main import (
|
|
cmd_buy_dlc,
|
|
cmd_reset,
|
|
cmd_setup,
|
|
cmd_unblock,
|
|
)
|
|
|
|
PKG = "steam_backlog_enforcer.main.misc"
|
|
_UNTIL = datetime(2026, 10, 3, 12, 0, tzinfo=UTC)
|
|
_FIVE_MINUTES = 5
|
|
|
|
|
|
class TestCmdUnblock:
|
|
"""Tests for cmd_unblock: it opens a timed store window."""
|
|
|
|
def test_default_window(self) -> None:
|
|
"""No argument opens the default-length store window and says until when."""
|
|
with (
|
|
patch(f"{PKG}.open_store_window", return_value=_UNTIL) as mock_open,
|
|
patch(f"{PKG}._echo") as mock_echo,
|
|
):
|
|
cmd_unblock(Config(), State())
|
|
assert mock_open.call_args.args[1] == DEFAULT_WINDOW_MINUTES
|
|
assert any("UNBLOCKED until" in str(c) for c in mock_echo.call_args_list)
|
|
|
|
def test_explicit_minutes(self) -> None:
|
|
"""A minutes argument sets the window length."""
|
|
with (
|
|
patch(f"{PKG}.open_store_window", return_value=_UNTIL) as mock_open,
|
|
patch(f"{PKG}._echo"),
|
|
):
|
|
cmd_unblock(Config(), State(), ["5"])
|
|
assert mock_open.call_args.args[1] == _FIVE_MINUTES
|
|
|
|
@pytest.mark.parametrize("raw", ["abc", "0", "31"])
|
|
def test_bad_minutes_exits(self, raw: str) -> None:
|
|
"""Non-numeric or out-of-range minutes print usage and exit."""
|
|
with (
|
|
patch(f"{PKG}._echo") as mock_echo,
|
|
pytest.raises(SystemExit),
|
|
):
|
|
cmd_unblock(Config(), State(), [raw])
|
|
assert any("Usage" in str(c) for c in mock_echo.call_args_list)
|
|
|
|
def test_unblock_failure_exits(self) -> None:
|
|
"""A failure to open the window is reported and exits."""
|
|
with (
|
|
patch(f"{PKG}.open_store_window", side_effect=RuntimeError("sudo?")),
|
|
patch(f"{PKG}._echo") as mock_echo,
|
|
pytest.raises(SystemExit),
|
|
):
|
|
cmd_unblock(Config(), State())
|
|
assert any("Failed" in str(c) for c in mock_echo.call_args_list)
|
|
|
|
|
|
class TestCmdBuyDlc:
|
|
"""cmd_buy_dlc is the default-length window, assigned game or not."""
|
|
|
|
def test_opens_default_window(self) -> None:
|
|
"""buy-dlc opens a window of the default length."""
|
|
with (
|
|
patch(f"{PKG}.open_store_window", return_value=_UNTIL) as mock_open,
|
|
patch(f"{PKG}._echo"),
|
|
):
|
|
cmd_buy_dlc(Config(), State())
|
|
assert mock_open.call_args.args[1] == DEFAULT_WINDOW_MINUTES
|
|
|
|
|
|
_RESET_PHRASE = "wipe all enforcer state"
|
|
|
|
|
|
def _reset(
|
|
state: State,
|
|
owned: object,
|
|
*,
|
|
typed: str = _RESET_PHRASE,
|
|
unhidden: int = 2,
|
|
) -> list[str]:
|
|
"""Run ``cmd_reset`` with *typed* as the answer; return what it printed."""
|
|
with (
|
|
patch("builtins.input", return_value=typed),
|
|
patch(f"{PKG}.get_all_owned_app_ids", side_effect=owned)
|
|
if isinstance(owned, Exception)
|
|
else patch(f"{PKG}.get_all_owned_app_ids", return_value=owned),
|
|
patch(f"{PKG}.unhide_all_games", return_value=unhidden),
|
|
patch(f"{PKG}._echo") as mock_echo,
|
|
patch.object(State, "save"),
|
|
):
|
|
cmd_reset(Config(), state)
|
|
return [str(c.args[0]) for c in mock_echo.call_args_list if c.args]
|
|
|
|
|
|
class TestCmdReset:
|
|
"""Tests for cmd_reset."""
|
|
|
|
def test_normal_reset(self) -> None:
|
|
"""Reset clears the assignment and history and unhides games."""
|
|
state = State(current_app_id=1, current_game_name="G", finished_app_ids=[1])
|
|
printed = _reset(state, [1, 2])
|
|
assert state.current_app_id is None
|
|
assert state.finished_app_ids == []
|
|
assert "Unhidden 2 games." in printed
|
|
|
|
def test_leaves_the_store_blocked(self) -> None:
|
|
"""Reset no longer lifts the store block: that needs ``unblock``."""
|
|
printed = _reset(State(current_app_id=1), [])
|
|
assert any("Store left blocked" in line for line in printed)
|
|
|
|
def test_wrong_phrase_aborts_and_keeps_state(self) -> None:
|
|
"""A wrong confirmation phrase aborts and keeps state."""
|
|
state = State(current_app_id=1)
|
|
printed = _reset(state, [1], typed="yes")
|
|
assert printed == ["Aborted."]
|
|
assert state.current_app_id == 1
|
|
|
|
def test_reports_the_backup_it_took(self) -> None:
|
|
"""Reset names the state backup it created."""
|
|
backup = MagicMock(id="20261010T000000Z-abcdef")
|
|
with patch(f"{PKG}.create_backup", return_value=backup):
|
|
printed = _reset(State(), [])
|
|
assert "State backed up as 20261010T000000Z-abcdef." in printed
|
|
|
|
def test_unhide_fails(self) -> None:
|
|
"""An unhide error is reported but the reset still happens."""
|
|
state = State(current_app_id=1)
|
|
printed = _reset(state, OSError("fail"))
|
|
assert any("could not unhide games: fail" in line for line in printed)
|
|
assert state.current_app_id is None
|
|
|
|
def test_unhide_returns_zero(self) -> None:
|
|
"""Zero unhidden games prints no unhidden count."""
|
|
state = State(current_app_id=1)
|
|
printed = _reset(state, [1, 2], unhidden=0)
|
|
assert not any("Unhidden" in line for line in printed)
|
|
|
|
|
|
class TestCmdSetup:
|
|
"""Tests for cmd_setup."""
|
|
|
|
def test_calls_interactive(self) -> None:
|
|
"""setup runs the interactive setup."""
|
|
with patch(f"{PKG}.interactive_setup") as mock_setup:
|
|
cmd_setup(Config(), State())
|
|
mock_setup.assert_called_once()
|