mirror of
https://github.com/kuhyx/steam-backlog-enforcer.git
synced 2026-11-11 19:13:49 +01:00
Each keeps its name and serves the moved names through a module __getattr__ (PEP 562) rather than a top-level re-export: every child imports back from its parent, so a plain import would be circular. The lookup goes through importlib.import_module, which is a call rather than an import statement, so it needs no linter suppression. Return type is a _Reexport alias -- mypy rejects `object` as not callable at the call sites, ruff rejects a bare Any. Also fixes two leaks where tests wrote to the user's real config, both caused by a redirected constant moving to a new module while conftest kept patching the old one: - APPROVED_EXCEPTIONS_FILE / EXCEPTION_AUDIT_LOG moved to _whitelist_locking; the suite had appended 27 fixture entries to the live exception_audit.log. - _OWNED_IDS_CACHE_FILE was never redirected at all: it is derived from CONFIG_DIR at import time, so it captured the real path before the fixture could patch it, and the suite overwrote the user's owned-games cache. Both are now verified by md5summing ~/.config/steam_backlog_enforcer before and after a full run, which is the only check that actually catches this -- the tests pass either way. 1347 tests, 100.00% coverage. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016A1EYiv7e7HGhET84oa9M3
133 lines
4.9 KiB
Python
133 lines
4.9 KiB
Python
"""Tests for the main CLI: done command dispatch."""
|
|
|
|
from unittest.mock import MagicMock, patch
|
|
|
|
from steam_backlog_enforcer.config import Config, State
|
|
from steam_backlog_enforcer.main import (
|
|
cmd_done,
|
|
)
|
|
from steam_backlog_enforcer.steam_api import GameInfo
|
|
|
|
PKG = "steam_backlog_enforcer.main"
|
|
CMD_DONE_PKG = "steam_backlog_enforcer._cmd_done_finalize"
|
|
|
|
|
|
class TestCmdDone:
|
|
"""Tests for cmd_done."""
|
|
|
|
def test_no_game_assigned(self) -> None:
|
|
with patch(f"{CMD_DONE_PKG}._echo") as mock_echo:
|
|
cmd_done(Config(), State())
|
|
assert any("No game" in str(c) for c in mock_echo.call_args_list)
|
|
|
|
def test_fetch_fails(self) -> None:
|
|
mock_client = MagicMock()
|
|
mock_client.refresh_single_game.return_value = None
|
|
state = State(current_app_id=1, current_game_name="G")
|
|
with (
|
|
patch(f"{CMD_DONE_PKG}.SteamAPIClient", return_value=mock_client),
|
|
patch(f"{CMD_DONE_PKG}._echo"),
|
|
):
|
|
cmd_done(Config(steam_api_key="k", steam_id="i"), state)
|
|
|
|
def test_not_complete_enforces(self) -> None:
|
|
game = GameInfo(
|
|
app_id=1,
|
|
name="G",
|
|
total_achievements=10,
|
|
unlocked_achievements=5,
|
|
playtime_minutes=60,
|
|
)
|
|
mock_client = MagicMock()
|
|
mock_client.refresh_single_game.return_value = game
|
|
state = State(current_app_id=1, current_game_name="G")
|
|
with (
|
|
patch(f"{CMD_DONE_PKG}.SteamAPIClient", return_value=mock_client),
|
|
patch(f"{CMD_DONE_PKG}._echo"),
|
|
patch(f"{CMD_DONE_PKG}.load_hltb_cache", return_value={1: 20.0}),
|
|
patch(f"{CMD_DONE_PKG}._enforce_on_done"),
|
|
):
|
|
cmd_done(Config(steam_api_key="k", steam_id="i"), state)
|
|
|
|
def test_complete_finalizes(self) -> None:
|
|
game = GameInfo(
|
|
app_id=1,
|
|
name="G",
|
|
total_achievements=10,
|
|
unlocked_achievements=10,
|
|
playtime_minutes=60,
|
|
)
|
|
mock_client = MagicMock()
|
|
mock_client.refresh_single_game.return_value = game
|
|
state = State(current_app_id=1, current_game_name="G")
|
|
with (
|
|
patch(f"{CMD_DONE_PKG}.SteamAPIClient", return_value=mock_client),
|
|
patch(f"{CMD_DONE_PKG}._echo"),
|
|
patch(f"{CMD_DONE_PKG}.load_hltb_cache", return_value={1: 10.0}),
|
|
patch(f"{CMD_DONE_PKG}._finalize_completion") as mock_final,
|
|
):
|
|
cmd_done(Config(steam_api_key="k", steam_id="i"), state)
|
|
mock_final.assert_called_once()
|
|
|
|
def test_hltb_cache_miss_fetches(self) -> None:
|
|
game = GameInfo(
|
|
app_id=1,
|
|
name="G",
|
|
total_achievements=10,
|
|
unlocked_achievements=5,
|
|
playtime_minutes=60,
|
|
)
|
|
mock_client = MagicMock()
|
|
mock_client.refresh_single_game.return_value = game
|
|
state = State(current_app_id=1, current_game_name="G")
|
|
with (
|
|
patch(f"{CMD_DONE_PKG}.SteamAPIClient", return_value=mock_client),
|
|
patch(f"{CMD_DONE_PKG}._echo"),
|
|
patch(f"{CMD_DONE_PKG}.load_hltb_cache", return_value={}),
|
|
patch(
|
|
f"{CMD_DONE_PKG}.fetch_hltb_times_cached",
|
|
return_value={1: 15.0},
|
|
),
|
|
patch(f"{CMD_DONE_PKG}._enforce_on_done"),
|
|
):
|
|
cmd_done(Config(steam_api_key="k", steam_id="i"), state)
|
|
|
|
def test_hltb_negative_no_display(self) -> None:
|
|
"""Covers the hours <= 0 branch (no HLTB estimate display)."""
|
|
game = GameInfo(
|
|
app_id=1,
|
|
name="G",
|
|
total_achievements=10,
|
|
unlocked_achievements=5,
|
|
playtime_minutes=60,
|
|
)
|
|
mock_client = MagicMock()
|
|
mock_client.refresh_single_game.return_value = game
|
|
state = State(current_app_id=1, current_game_name="G")
|
|
with (
|
|
patch(f"{CMD_DONE_PKG}.SteamAPIClient", return_value=mock_client),
|
|
patch(f"{CMD_DONE_PKG}._echo"),
|
|
patch(f"{CMD_DONE_PKG}.load_hltb_cache", return_value={1: -1.0}),
|
|
patch(f"{CMD_DONE_PKG}._enforce_on_done"),
|
|
):
|
|
cmd_done(Config(steam_api_key="k", steam_id="i"), state)
|
|
|
|
def test_reassign_returns_true(self) -> None:
|
|
game = GameInfo(
|
|
app_id=1,
|
|
name="G",
|
|
total_achievements=10,
|
|
unlocked_achievements=10,
|
|
playtime_minutes=60,
|
|
)
|
|
mock_client = MagicMock()
|
|
mock_client.refresh_single_game.return_value = game
|
|
state = State(current_app_id=1, current_game_name="G")
|
|
with (
|
|
patch(f"{CMD_DONE_PKG}.SteamAPIClient", return_value=mock_client),
|
|
patch(f"{CMD_DONE_PKG}._echo"),
|
|
patch(f"{CMD_DONE_PKG}.load_hltb_cache", return_value={1: 50.0}),
|
|
patch(f"{CMD_DONE_PKG}._finalize_completion"),
|
|
):
|
|
cmd_done(Config(steam_api_key="k", steam_id="i"), state)
|