mirror of
https://github.com/kuhyx/steam-backlog-enforcer.git
synced 2026-11-11 14:27:33 +01:00
Tests isolate FOCUS_PROJECT_DATA so a live project never leaks in. Also bumps the freshness gate's stale dev pins and websockets. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WzxdWYcmik6APnHfc9gEVy
242 lines
9.5 KiB
Python
242 lines
9.5 KiB
Python
"""Safety conftest: prevent tests from touching real Steam/config files.
|
|
|
|
Redirects all filesystem paths used by the steam_backlog_enforcer package
|
|
to temporary directories. This stops tests from accidentally:
|
|
- Deleting real game files via uninstall_other_games / uninstall_game
|
|
- Overwriting ~/.config/steam_backlog_enforcer/state.json (losing the
|
|
user's current assignment)
|
|
- Reading real appmanifest files from ~/.local/share/Steam/steamapps
|
|
- Modifying /etc/hosts via the store blocker
|
|
- Corrupting the HLTB cache on disk
|
|
- Launching real Steam or calling real subprocess commands
|
|
- Deleting real ~/.steam, ~/.local/share/Steam via the remnant purge
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from typing import TYPE_CHECKING
|
|
from unittest.mock import MagicMock, patch
|
|
|
|
import pytest
|
|
|
|
# Imported for the autouse side effect (naming them registers them).
|
|
from steam_backlog_enforcer.tests._isolate_control_plane import (
|
|
_forget_hltb_search_url,
|
|
_isolate_control_plane,
|
|
_isolate_cover_art,
|
|
)
|
|
from steam_backlog_enforcer.tests._isolate_incidents import _isolate_incidents
|
|
from steam_backlog_enforcer.tests._isolate_leisure import _isolate_leisure
|
|
from steam_backlog_enforcer.tests._isolate_playtime import _isolate_playtime
|
|
from steam_backlog_enforcer.tests._isolate_reading import _isolate_reading
|
|
from steam_backlog_enforcer.tests._isolate_shadow import _isolate_shadow
|
|
from steam_backlog_enforcer.tests._no_fast_install import _no_fast_install
|
|
from steam_backlog_enforcer.tests._no_live_focus_project import _no_live_focus_project
|
|
from steam_backlog_enforcer.tests._no_real_effects import (
|
|
_no_live_games,
|
|
_no_real_effects,
|
|
)
|
|
from steam_backlog_enforcer.tests._no_subprocess import _block_real_subprocesses
|
|
from steam_backlog_enforcer.tests._no_systemd_run import _no_systemd_run
|
|
from steam_backlog_enforcer.tests._no_workout_http import _no_workout_http
|
|
|
|
# Re-exported so ruff --fix does not delete the imports above: pytest
|
|
# registers autouse fixtures by name, so they look unused to the linter.
|
|
__all__ = [
|
|
"_block_real_subprocesses",
|
|
"_forget_hltb_search_url",
|
|
"_isolate_control_plane",
|
|
"_isolate_cover_art",
|
|
"_isolate_incidents",
|
|
"_isolate_leisure",
|
|
"_isolate_playtime",
|
|
"_isolate_reading",
|
|
"_isolate_shadow",
|
|
"_no_fast_install",
|
|
"_no_live_focus_project",
|
|
"_no_live_games",
|
|
"_no_real_effects",
|
|
"_no_systemd_run",
|
|
"_no_workout_http",
|
|
]
|
|
|
|
from steam_backlog_enforcer.tests._total_block_paths import (
|
|
BLOCK,
|
|
HOSTS,
|
|
IPTABLES,
|
|
PURGE,
|
|
Paths,
|
|
build_paths,
|
|
)
|
|
|
|
if TYPE_CHECKING:
|
|
from collections.abc import Iterator
|
|
from pathlib import Path
|
|
|
|
|
|
@pytest.fixture(autouse=True)
|
|
def _isolate_filesystem(tmp_path: Path) -> Iterator[None]:
|
|
"""Redirect all real filesystem paths to a temporary directory.
|
|
|
|
Individual tests that also patch these paths will simply override
|
|
this fixture's patches for the duration of their own ``with`` block.
|
|
"""
|
|
fake_config = tmp_path / "config"
|
|
fake_config.mkdir()
|
|
fake_steamapps = tmp_path / "steamapps"
|
|
fake_steamapps.mkdir()
|
|
fake_hosts = tmp_path / "hosts"
|
|
|
|
with (
|
|
# Config / state / snapshot paths (used by State.save, Config.save, etc.)
|
|
patch(
|
|
"steam_backlog_enforcer.config.CONFIG_DIR",
|
|
fake_config,
|
|
),
|
|
patch(
|
|
"steam_backlog_enforcer.config.CONFIG_FILE",
|
|
fake_config / "config.json",
|
|
),
|
|
patch(
|
|
"steam_backlog_enforcer.config.STATE_FILE",
|
|
fake_config / "state.json",
|
|
),
|
|
patch(
|
|
"steam_backlog_enforcer.config.SNAPSHOT_FILE",
|
|
fake_config / "snapshot.json",
|
|
),
|
|
# Steam game manifests / install dirs. STEAMAPPS_PATH is canonical in
|
|
# _steam_state; game_install and _steam_client import it by name, so
|
|
# every binding needs the patch (same gotcha as HOSTS_FILE below).
|
|
patch(
|
|
"steam_backlog_enforcer._steam_state.STEAMAPPS_PATH",
|
|
fake_steamapps,
|
|
),
|
|
patch(
|
|
"steam_backlog_enforcer.game_install.STEAMAPPS_PATH",
|
|
fake_steamapps,
|
|
),
|
|
patch(
|
|
"steam_backlog_enforcer._steam_client.STEAMAPPS_PATH",
|
|
fake_steamapps,
|
|
),
|
|
# HLTB cache file (computed at import time from CONFIG_DIR, so
|
|
# patching CONFIG_DIR alone does not redirect it)
|
|
patch(
|
|
"steam_backlog_enforcer._hltb_types.HLTB_CACHE_FILE",
|
|
fake_config / "hltb_cache.json",
|
|
),
|
|
# /etc/hosts (store blocker + total block - each module has its own
|
|
# `from ... import HOSTS_FILE` binding, so each needs its own patch)
|
|
patch(
|
|
"steam_backlog_enforcer.store_blocker.HOSTS_FILE",
|
|
fake_hosts,
|
|
),
|
|
patch(
|
|
"steam_backlog_enforcer._total_block_hosts.HOSTS_FILE",
|
|
fake_hosts,
|
|
),
|
|
patch(
|
|
"steam_backlog_enforcer.config.HOSTS_FILE",
|
|
fake_hosts,
|
|
),
|
|
# Total-block lock + IP cache (computed at import time from
|
|
# CONFIG_DIR, so patching CONFIG_DIR alone does not redirect them -
|
|
# same gotcha as HLTB_CACHE_FILE above. A real total-block lock may
|
|
# be active on the host machine; tests must never touch it.)
|
|
patch(
|
|
"steam_backlog_enforcer._total_block.TOTAL_BLOCK_LOCK_FILE",
|
|
fake_config / "total_block_lock.json",
|
|
),
|
|
patch(
|
|
"steam_backlog_enforcer._total_block_iptables._IPTABLES_IP_CACHE_FILE",
|
|
fake_config / "total_block_ip_cache.json",
|
|
),
|
|
patch(
|
|
"steam_backlog_enforcer._total_block_purge._STEAM_PURGE_LOG_FILE",
|
|
fake_config / "total_block_purge_log.json",
|
|
),
|
|
# Steam/Proton remnant paths (real ~/.steam, ~/.local/share/Steam,
|
|
# etc. - _remove_steam_remnants() deletes these, so tests must never
|
|
# see the real ones)
|
|
patch(
|
|
"steam_backlog_enforcer._total_block_purge._STEAM_REMNANT_PATHS",
|
|
(
|
|
tmp_path / "fake_home" / ".steam",
|
|
tmp_path / "fake_home" / "steam",
|
|
tmp_path / "fake_home" / ".local" / "share" / "Steam",
|
|
tmp_path / "fake_home" / ".steampath",
|
|
tmp_path / "fake_home" / ".steampid",
|
|
tmp_path / "fake_home" / ".config" / "steamtinkerlaunch",
|
|
tmp_path / "fake_home" / ".config" / "CSDSteamBuild",
|
|
),
|
|
),
|
|
# Derived from CONFIG_DIR at import time, so it captures the REAL path
|
|
# before this fixture redirects CONFIG_DIR. Without it the suite
|
|
# rewrites the user's owned-games cache.
|
|
patch(
|
|
"steam_backlog_enforcer._owned_apps_cache._OWNED_IDS_CACHE_FILE",
|
|
fake_config / "owned_app_ids_cache.json",
|
|
),
|
|
# Whitelist exception files (module-level constants; these live in
|
|
# _whitelist_locking, which is where the code that reads them is)
|
|
patch(
|
|
"steam_backlog_enforcer._whitelist_locking.APPROVED_EXCEPTIONS_FILE",
|
|
fake_config / "approved_exceptions.json",
|
|
),
|
|
patch(
|
|
"steam_backlog_enforcer._whitelist_locking.EXCEPTION_AUDIT_LOG",
|
|
fake_config / "exception_audit.log",
|
|
),
|
|
# _enforce_loop imports CONFIG_FILE directly; patch the local binding so
|
|
# lock_enforcement_files() uses the tmp path instead of the real one.
|
|
patch(
|
|
"steam_backlog_enforcer._enforce_loop.CONFIG_FILE",
|
|
fake_config / "config.json",
|
|
),
|
|
# Generic earners resolve their ledgers under the real home directory.
|
|
patch("steam_backlog_enforcer._ledger_earners.LEDGER_HOME", tmp_path),
|
|
):
|
|
yield
|
|
|
|
|
|
@pytest.fixture(autouse=True)
|
|
def _no_real_sleep() -> Iterator[None]:
|
|
"""No-op every ``time.sleep`` used by the package.
|
|
|
|
Modules call ``time.sleep`` to pace Steam launch / install retries /
|
|
rate limits; tests that observe sleep override these patches.
|
|
"""
|
|
noop = MagicMock()
|
|
with (
|
|
patch("steam_backlog_enforcer._steam_client.time.sleep", noop),
|
|
patch("steam_backlog_enforcer._steam_launch.time.sleep", noop),
|
|
patch("steam_backlog_enforcer._steam_api_client.time.sleep", noop),
|
|
# The loop waits on an inotify wake, not time.sleep.
|
|
patch("steam_backlog_enforcer._enforce_loop.open_file_wake", noop),
|
|
):
|
|
yield
|
|
|
|
|
|
# ──────────────────────────────────────────────────────────────
|
|
# Total-block path redirection
|
|
#
|
|
# Lives here rather than in a helper module because pytest resolves
|
|
# fixtures by name: an imported fixture looks unused to ruff, and this
|
|
# repo runs `ruff --fix --unsafe-fixes`, which deletes the import.
|
|
# ──────────────────────────────────────────────────────────────
|
|
|
|
|
|
@pytest.fixture(autouse=True)
|
|
def total_block_paths(tmp_path: Path) -> Iterator[Paths]:
|
|
"""Redirect every total-block path constant to tmp_path for one test."""
|
|
built = build_paths(tmp_path)
|
|
with (
|
|
patch(f"{BLOCK}.TOTAL_BLOCK_LOCK_FILE", built.lock_file),
|
|
patch(f"{IPTABLES}._IPTABLES_IP_CACHE_FILE", built.ip_cache_file),
|
|
patch(f"{HOSTS}.HOSTS_FILE", built.hosts_file),
|
|
patch(f"{PURGE}._STEAM_PURGE_LOG_FILE", built.purge_log_file),
|
|
patch(f"{PURGE}._STEAM_REMNANT_PATHS", built.remnant_paths),
|
|
):
|
|
yield built
|