mirror of
https://github.com/kuhyx/steam-backlog-enforcer.git
synced 2026-11-11 18:13:49 +01:00
main.py became main/ with __main__.py, so the invocation string baked into
run.sh and the systemd unit (`python -m steam_backlog_enforcer.main`) stays
byte-identical and nothing needs redeploying. Commands are grouped into
sibling modules (status, install, picks, abandon, misc), with _shared.py as
a leaf holding the lock helpers and constants both need, and _registry.py
holding the command tables. main/__init__.py keeps every public and private
name importable, so `from steam_backlog_enforcer.main import X` is unchanged.
The subtle part is patch targets. The test suite builds them as
f"{PKG}.{name}", so a grep for the literal module path found nothing -- but
once a command moves to a submodule it resolves its dependencies from that
module's globals, and patching the main package's re-export silently stops
biting. Measured which of the 36 patched symbols moved: 8 could have passed
vacuously. Rather than repair PKG per class, the five test_main*.py files
(all cap violators anyway) were regrouped to mirror the production modules,
so each file has exactly one correct PKG. Verified by mutation: breaking a
line in status/misc/picks/_shared each makes the corresponding tests fail.
test_web_server.py patched main_mod.serve, which no longer intercepts
cmd_serve; repointed at main.misc and confirmed it fails when the call is
removed. Added tests for the __main__ shim, which coverage would otherwise
report at 0% under fail_under = 100.
Shared test builders live in tests/_main_helpers.py, reachable because the
Phase 0 name-tests-test exclude allows tests/_*.py.
1290 tests pass; every main/ module reports 100% coverage; `main`, `status`,
`bogus` and `--status` produce byte-identical output and exit codes to
pre-refactor. 55 -> 49 files over the cap.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PPpmMpTSkp8UziRc5u8B7P
144 lines
4.6 KiB
Python
144 lines
4.6 KiB
Python
"""Tests for the main CLI: store, reset, setup, exception and block-gaming commands."""
|
|
|
|
from unittest.mock import patch
|
|
|
|
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"
|
|
|
|
|
|
class TestCmdUnblock:
|
|
"""Tests for cmd_unblock."""
|
|
|
|
def test_success(self) -> None:
|
|
with (
|
|
patch(f"{PKG}.unblock_store", return_value=True),
|
|
patch(f"{PKG}._echo"),
|
|
):
|
|
cmd_unblock(Config(), State())
|
|
|
|
def test_fail(self) -> None:
|
|
with (
|
|
patch(f"{PKG}.unblock_store", return_value=False),
|
|
patch(f"{PKG}._echo") as mock_echo,
|
|
):
|
|
cmd_unblock(Config(), State())
|
|
assert any("Failed" in str(c) for c in mock_echo.call_args_list)
|
|
|
|
|
|
class TestCmdBuyDlc:
|
|
"""Tests for cmd_buy_dlc."""
|
|
|
|
def test_no_game(self) -> None:
|
|
with patch(f"{PKG}._echo") as mock_echo:
|
|
cmd_buy_dlc(Config(), State())
|
|
assert any("No game" in str(c) for c in mock_echo.call_args_list)
|
|
|
|
def test_unblock_fails(self) -> None:
|
|
state = State(current_app_id=1, current_game_name="G")
|
|
with (
|
|
patch(f"{PKG}.unblock_store", return_value=False),
|
|
patch(f"{PKG}._echo"),
|
|
):
|
|
cmd_buy_dlc(Config(), state)
|
|
|
|
def test_success_reblock(self) -> None:
|
|
state = State(current_app_id=1, current_game_name="G")
|
|
config = Config(block_store=True)
|
|
with (
|
|
patch(f"{PKG}.unblock_store", return_value=True),
|
|
patch(f"{PKG}.block_store", return_value=True),
|
|
patch(f"{PKG}.restart_steam"),
|
|
patch(f"{PKG}._echo"),
|
|
patch("builtins.input", return_value=""),
|
|
):
|
|
cmd_buy_dlc(config, state)
|
|
|
|
def test_reblock_fails(self) -> None:
|
|
state = State(current_app_id=1, current_game_name="G")
|
|
config = Config(block_store=True)
|
|
with (
|
|
patch(f"{PKG}.unblock_store", return_value=True),
|
|
patch(f"{PKG}.block_store", return_value=False),
|
|
patch(f"{PKG}._echo") as mock_echo,
|
|
patch("builtins.input", return_value=""),
|
|
):
|
|
cmd_buy_dlc(config, state)
|
|
assert any("Warning" in str(c) for c in mock_echo.call_args_list)
|
|
|
|
def test_no_reblock(self) -> None:
|
|
state = State(current_app_id=1, current_game_name="G")
|
|
config = Config(block_store=False)
|
|
with (
|
|
patch(f"{PKG}.unblock_store", return_value=True),
|
|
patch(f"{PKG}._echo"),
|
|
patch("builtins.input", return_value=""),
|
|
):
|
|
cmd_buy_dlc(config, state)
|
|
|
|
|
|
class TestCmdReset:
|
|
"""Tests for cmd_reset."""
|
|
|
|
def test_normal_reset(self) -> None:
|
|
state = State(current_app_id=1, current_game_name="G", finished_app_ids=[1])
|
|
with (
|
|
patch(f"{PKG}.unblock_store"),
|
|
patch(f"{PKG}.get_all_owned_app_ids", return_value=[1, 2]),
|
|
patch(f"{PKG}.unhide_all_games", return_value=2),
|
|
patch(f"{PKG}._echo"),
|
|
patch.object(State, "save"),
|
|
):
|
|
cmd_reset(Config(), state)
|
|
assert state.current_app_id is None
|
|
assert state.finished_app_ids == []
|
|
|
|
def test_unhide_fails(self) -> None:
|
|
state = State(current_app_id=1)
|
|
with (
|
|
patch(f"{PKG}.unblock_store"),
|
|
patch(
|
|
f"{PKG}.get_all_owned_app_ids",
|
|
side_effect=OSError("fail"),
|
|
),
|
|
patch(f"{PKG}._echo"),
|
|
patch.object(State, "save"),
|
|
):
|
|
cmd_reset(Config(), state)
|
|
|
|
def test_unhide_returns_zero(self) -> None:
|
|
state = State(current_app_id=1)
|
|
with (
|
|
patch(f"{PKG}.unblock_store"),
|
|
patch(f"{PKG}.get_all_owned_app_ids", return_value=[1, 2]),
|
|
patch(f"{PKG}.unhide_all_games", return_value=0),
|
|
patch(f"{PKG}._echo"),
|
|
patch.object(State, "save"),
|
|
):
|
|
cmd_reset(Config(), state)
|
|
|
|
def test_no_owned_ids(self) -> None:
|
|
state = State(current_app_id=1)
|
|
with (
|
|
patch(f"{PKG}.unblock_store"),
|
|
patch(f"{PKG}.get_all_owned_app_ids", return_value=[]),
|
|
patch(f"{PKG}._echo"),
|
|
patch.object(State, "save"),
|
|
):
|
|
cmd_reset(Config(), state)
|
|
|
|
|
|
class TestCmdSetup:
|
|
"""Tests for cmd_setup."""
|
|
|
|
def test_calls_interactive(self) -> None:
|
|
with patch(f"{PKG}.interactive_setup") as mock_setup:
|
|
cmd_setup(Config(), State())
|
|
mock_setup.assert_called_once()
|