mirror of
https://github.com/kuhyx/steam-backlog-enforcer.git
synced 2026-11-10 19:27:04 +01:00
require_snapshot_games / snapshot_game_name (_snapshot), apply_configured_pick (_actions) and hide_others_and_report (library_hider) replace copies spread across the CLI, MCP and stats paths. Tests retargeted to the new owners; test_main_pick_manual split to stay under 250 lines. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01D55DvFEHiwcPN59kwPqwiU
191 lines
7.7 KiB
Python
191 lines
7.7 KiB
Python
"""Tests for the main CLI: status and list commands."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from datetime import UTC, datetime, timedelta
|
|
from unittest.mock import patch
|
|
|
|
from steam_backlog_enforcer._total_block import TotalBlockStatus
|
|
from steam_backlog_enforcer.config import Config, State
|
|
from steam_backlog_enforcer.main import (
|
|
cmd_list,
|
|
cmd_status,
|
|
)
|
|
from steam_backlog_enforcer.tests._main_helpers import (
|
|
ACTIVE_STATUS,
|
|
INACTIVE_STATUS,
|
|
locked_state,
|
|
snap,
|
|
)
|
|
|
|
PKG = "steam_backlog_enforcer.main.status"
|
|
|
|
|
|
class TestCmdStatus:
|
|
"""Tests for cmd_status."""
|
|
|
|
def test_with_game(self) -> None:
|
|
state = State(current_app_id=440, current_game_name="TF2")
|
|
with (
|
|
patch(f"{PKG}.is_store_blocked", return_value=True),
|
|
patch(f"{PKG}.get_installed_games", return_value=[(440, "TF2")]),
|
|
patch(f"{PKG}.report_completion", return_value=[]),
|
|
patch(f"{PKG}._echo"),
|
|
):
|
|
cmd_status(Config(), state)
|
|
|
|
def test_no_game(self) -> None:
|
|
with (
|
|
patch(f"{PKG}.is_store_blocked", return_value=False),
|
|
patch(f"{PKG}.get_installed_games", return_value=[]),
|
|
patch(f"{PKG}.report_completion", return_value=[]),
|
|
patch(f"{PKG}._echo"),
|
|
):
|
|
cmd_status(Config(), State())
|
|
|
|
|
|
class TestCmdList:
|
|
"""Tests for cmd_list."""
|
|
|
|
def test_no_snapshot(self) -> None:
|
|
with (
|
|
patch("steam_backlog_enforcer._snapshot.load_snapshot", return_value=None),
|
|
patch(f"{PKG}.report_completion", return_value=[]),
|
|
# The "run scan" hint is printed by the shared snapshot helper.
|
|
patch("steam_backlog_enforcer._snapshot._echo") as mock_echo,
|
|
):
|
|
cmd_list(Config(), State())
|
|
assert any("No snapshot" in str(c) for c in mock_echo.call_args_list)
|
|
|
|
def test_with_games(self) -> None:
|
|
snapshot = [
|
|
snap(1, "A", 10, 5, 20.0),
|
|
snap(2, "B", 10, 10, 10.0),
|
|
snap(3, "C", 10, 3, -1),
|
|
]
|
|
state = State(current_app_id=1)
|
|
with (
|
|
patch(
|
|
"steam_backlog_enforcer._snapshot.load_snapshot", return_value=snapshot
|
|
),
|
|
patch(f"{PKG}.report_completion", return_value=[]),
|
|
patch(f"{PKG}._echo"),
|
|
):
|
|
cmd_list(Config(), state)
|
|
|
|
def test_many_games(self) -> None:
|
|
snapshot = [snap(i, f"Game{i}") for i in range(60)]
|
|
with (
|
|
patch(
|
|
"steam_backlog_enforcer._snapshot.load_snapshot", return_value=snapshot
|
|
),
|
|
patch(f"{PKG}.report_completion", return_value=[]),
|
|
patch(f"{PKG}._echo") as mock_echo,
|
|
):
|
|
cmd_list(Config(), State())
|
|
assert any("more" in str(c) for c in mock_echo.call_args_list)
|
|
|
|
|
|
class TestCmdStatusLockHint:
|
|
def test_shows_lock_hint_when_locked(self) -> None:
|
|
state = locked_state()
|
|
with (
|
|
patch(f"{PKG}.is_store_blocked", return_value=False),
|
|
patch(f"{PKG}.get_installed_games", return_value=[]),
|
|
patch(f"{PKG}.report_completion", return_value=[]),
|
|
patch(f"{PKG}._echo") as mock_echo,
|
|
):
|
|
cmd_status(Config(), state)
|
|
output = " ".join(str(c) for c in mock_echo.call_args_list)
|
|
assert "MANUAL PICK LOCK" in output
|
|
|
|
def test_no_lock_hint_when_not_locked(self) -> None:
|
|
with (
|
|
patch(f"{PKG}.is_store_blocked", return_value=False),
|
|
patch(f"{PKG}.get_installed_games", return_value=[]),
|
|
patch(f"{PKG}.report_completion", return_value=[]),
|
|
patch(f"{PKG}._echo") as mock_echo,
|
|
):
|
|
cmd_status(Config(), State())
|
|
output = " ".join(str(c) for c in mock_echo.call_args_list)
|
|
assert "MANUAL PICK LOCK" not in output
|
|
|
|
|
|
# ──────────────────────────────────────────────────────────────
|
|
# cmd_abandon_pick (grace period escape hatch)
|
|
# ──────────────────────────────────────────────────────────────
|
|
|
|
# A pick made yesterday / one made over a week ago. Both are abandonable.
|
|
_RECENT_PICK = (datetime.now(UTC) - timedelta(days=1)).isoformat()
|
|
|
|
|
|
def _abandonable_state(app_id: int = 100, started_at: str = _RECENT_PICK) -> State:
|
|
state = locked_state(app_id=app_id, started_at=started_at)
|
|
state.current_app_id = app_id
|
|
state.current_game_name = state.manual_pick_game_name
|
|
return state
|
|
|
|
|
|
class TestCmdStatusTotalBlock:
|
|
def test_shows_total_block_when_active(self) -> None:
|
|
with (
|
|
patch(f"{PKG}.get_total_block_status", return_value=ACTIVE_STATUS),
|
|
patch(f"{PKG}.is_store_blocked", return_value=False),
|
|
patch(f"{PKG}.get_installed_games", return_value=[]),
|
|
patch(f"{PKG}.report_completion", return_value=[]),
|
|
patch(f"{PKG}._echo") as mock_echo,
|
|
):
|
|
cmd_status(Config(), State())
|
|
output = " ".join(str(c) for c in mock_echo.call_args_list)
|
|
assert "TOTAL GAMING BLOCK ACTIVE" in output
|
|
assert "Days remaining" in output
|
|
|
|
def test_no_total_block_section_when_inactive(self) -> None:
|
|
with (
|
|
patch(f"{PKG}.get_total_block_status", return_value=INACTIVE_STATUS),
|
|
patch(f"{PKG}.is_store_blocked", return_value=False),
|
|
patch(f"{PKG}.get_installed_games", return_value=[]),
|
|
patch(f"{PKG}.report_completion", return_value=[]),
|
|
patch(f"{PKG}._echo") as mock_echo,
|
|
):
|
|
cmd_status(Config(), State())
|
|
output = " ".join(str(c) for c in mock_echo.call_args_list)
|
|
assert "TOTAL GAMING BLOCK" not in output
|
|
|
|
def test_active_without_until_skips_remaining_time(self) -> None:
|
|
status = TotalBlockStatus(
|
|
active=True, started_at=None, until=None, days=1, days_remaining=0.5
|
|
)
|
|
with (
|
|
patch(f"{PKG}.get_total_block_status", return_value=status),
|
|
patch(f"{PKG}.is_store_blocked", return_value=False),
|
|
patch(f"{PKG}.get_installed_games", return_value=[]),
|
|
patch(f"{PKG}.report_completion", return_value=[]),
|
|
patch(f"{PKG}._echo") as mock_echo,
|
|
):
|
|
cmd_status(Config(), State())
|
|
output = " ".join(str(c) for c in mock_echo.call_args_list)
|
|
assert "TOTAL GAMING BLOCK ACTIVE" in output
|
|
assert "Days remaining" not in output
|
|
|
|
|
|
# ──────────────────────────────────────────────────────────────
|
|
# main() dispatch to the daily-gaming-budget commands
|
|
# ──────────────────────────────────────────────────────────────
|
|
|
|
|
|
class TestCmdStatusCompletionSweepIsNonFatal:
|
|
"""status stays usable when the completion sweep cannot run."""
|
|
|
|
def test_reports_failure_and_still_renders(self) -> None:
|
|
with (
|
|
patch(f"{PKG}.report_completion", side_effect=OSError("state read-only")),
|
|
patch(f"{PKG}.is_store_blocked", return_value=True),
|
|
patch(f"{PKG}.get_installed_games", return_value=[]),
|
|
patch(f"{PKG}._echo") as mock_echo,
|
|
):
|
|
cmd_status(Config(), State(current_app_id=440, current_game_name="TF2"))
|
|
output = " ".join(str(c) for c in mock_echo.call_args_list)
|
|
assert "Could not refresh pick completion: state read-only" in output
|
|
assert "Store blocked" in output
|