mirror of
https://github.com/kuhyx/steam-backlog-enforcer.git
synced 2026-11-11 21:13:48 +01:00
Leaf-set extraction only, so every new import is one-way and no cycle is
possible:
_stats.py 523 -> 233 + _stats_types (the shared _GameTimes dataclass,
imported by parent and helpers alike),
_stats_gathering, _stats_display
scanning.py 521 -> 248 + _scanning_candidates, _scanning_assign,
_scanning_tampering
_GameTimes and the four moved scanning helpers are re-exported from their
original modules via __all__ (mypy --no-implicit-reexport needs them listed),
so callers and tests keep their existing import paths.
Test patch targets were retargeted per file from the pytest failure list —
the target is the module whose source now contains the function under test.
Note TestPrintScenario and TestDoCheck did NOT move, so they keep the
original target while the rest of their files change.
1344 tests, 100.00% coverage. ./run.sh status and ./run.sh stats verified
against the live install; service at 0 restarts.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016A1EYiv7e7HGhET84oa9M3
135 lines
4.3 KiB
Python
135 lines
4.3 KiB
Python
"""Tests for scanning module — part 2 (missing coverage)."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from typing import Any
|
|
from unittest.mock import MagicMock, patch
|
|
|
|
from steam_backlog_enforcer.config import Config, State
|
|
from steam_backlog_enforcer.scanning import (
|
|
_check_game_tampering,
|
|
detect_tampering,
|
|
)
|
|
|
|
PKG = "steam_backlog_enforcer._scanning_tampering"
|
|
|
|
|
|
def _entry(
|
|
app_id: int = 1,
|
|
name: str = "G",
|
|
total: int = 10,
|
|
unlocked: int = 5,
|
|
playtime: int = 60,
|
|
) -> dict[str, Any]:
|
|
return {
|
|
"app_id": app_id,
|
|
"name": name,
|
|
"total_achievements": total,
|
|
"unlocked_achievements": unlocked,
|
|
"playtime_minutes": playtime,
|
|
}
|
|
|
|
|
|
class TestCheckGameTampering:
|
|
"""Tests for _check_game_tampering."""
|
|
|
|
def test_current_game_skipped(self) -> None:
|
|
state = State(current_app_id=1)
|
|
result = _check_game_tampering(MagicMock(), _entry(app_id=1), state)
|
|
assert result is None
|
|
|
|
def test_already_complete_skipped(self) -> None:
|
|
state = State()
|
|
result = _check_game_tampering(
|
|
MagicMock(),
|
|
_entry(unlocked=10, total=10),
|
|
state,
|
|
)
|
|
assert result is None
|
|
|
|
def test_zero_playtime_skipped(self) -> None:
|
|
state = State()
|
|
result = _check_game_tampering(
|
|
MagicMock(),
|
|
_entry(playtime=0),
|
|
state,
|
|
)
|
|
assert result is None
|
|
|
|
def test_no_new_achievements(self) -> None:
|
|
client = MagicMock()
|
|
game = MagicMock()
|
|
game.unlocked_achievements = 5
|
|
client.refresh_single_game.return_value = game
|
|
state = State()
|
|
result = _check_game_tampering(client, _entry(unlocked=5), state)
|
|
assert result is None
|
|
|
|
def test_tampering_detected(self) -> None:
|
|
client = MagicMock()
|
|
game = MagicMock()
|
|
game.unlocked_achievements = 8
|
|
client.refresh_single_game.return_value = game
|
|
state = State()
|
|
entry = _entry(app_id=99, name="Cheated", unlocked=5)
|
|
result = _check_game_tampering(client, entry, state)
|
|
assert result is not None
|
|
assert result == ("Cheated", 99, 3)
|
|
|
|
def test_refresh_returns_none(self) -> None:
|
|
client = MagicMock()
|
|
client.refresh_single_game.return_value = None
|
|
state = State()
|
|
result = _check_game_tampering(client, _entry(), state)
|
|
assert result is None
|
|
|
|
|
|
class TestDetectTampering:
|
|
"""Tests for detect_tampering."""
|
|
|
|
def test_no_snapshot(self) -> None:
|
|
with patch(f"{PKG}.load_snapshot", return_value=None):
|
|
detect_tampering(Config(steam_api_key="k", steam_id="i"), State())
|
|
|
|
def test_no_tampering(self) -> None:
|
|
entries = [_entry(app_id=1)]
|
|
with (
|
|
patch(f"{PKG}.load_snapshot", return_value=entries),
|
|
patch(f"{PKG}.SteamAPIClient"),
|
|
patch(f"{PKG}._check_game_tampering", return_value=None),
|
|
patch(f"{PKG}._echo"),
|
|
):
|
|
detect_tampering(Config(steam_api_key="k", steam_id="i"), State())
|
|
|
|
def test_tampering_found(self) -> None:
|
|
entries = [_entry(app_id=1, name="BadGame")]
|
|
with (
|
|
patch(f"{PKG}.load_snapshot", return_value=entries),
|
|
patch(f"{PKG}.SteamAPIClient"),
|
|
patch(
|
|
f"{PKG}._check_game_tampering",
|
|
return_value=("BadGame", 1, 3),
|
|
),
|
|
patch(f"{PKG}._echo") as mock_echo,
|
|
patch(f"{PKG}.send_notification"),
|
|
):
|
|
detect_tampering(Config(steam_api_key="k", steam_id="i"), State())
|
|
assert any("TAMPERING" in str(c) for c in mock_echo.call_args_list)
|
|
|
|
def test_stops_at_limit(self) -> None:
|
|
"""Stops after _TAMPER_CHECK_LIMIT suspicious games."""
|
|
entries = [_entry(app_id=i, name=f"G{i}") for i in range(10)]
|
|
with (
|
|
patch(f"{PKG}.load_snapshot", return_value=entries),
|
|
patch(f"{PKG}.SteamAPIClient"),
|
|
patch(
|
|
f"{PKG}._check_game_tampering",
|
|
return_value=("Game", 1, 1),
|
|
) as mock_check,
|
|
patch(f"{PKG}._echo"),
|
|
patch(f"{PKG}.send_notification"),
|
|
):
|
|
detect_tampering(Config(steam_api_key="k", steam_id="i"), State())
|
|
# Should stop after 3 (_TAMPER_CHECK_LIMIT)
|
|
assert mock_check.call_count == 3
|