chore: set up as standalone repo

Extracted from testsAndMisc monorepo. Changes:
- Rewrote imports from python_pkg.steam_backlog_enforcer.* → steam_backlog_enforcer.*
- Moved run.sh, install.sh, README.md, service file to repo root
- Added standalone pyproject.toml, requirements.txt, .pre-commit-config.yaml, .gitignore
- Added GitHub Actions CI workflows (tests + pre-commit)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Krzysztof kuhy Rudnicki 2026-05-28 07:21:29 +02:00
parent 48b609e1a3
commit 551b8a4f95
57 changed files with 1025 additions and 603 deletions

19
.github/workflows/pre-commit.yml vendored Normal file
View File

@ -0,0 +1,19 @@
name: pre-commit
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
pre-commit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.12"
- name: Install dependencies
run: pip install -r requirements.txt
- uses: pre-commit/action@v3.0.1

33
.github/workflows/python-tests.yml vendored Normal file
View File

@ -0,0 +1,33 @@
name: Tests
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.10", "3.11", "3.12"]
steps:
- uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
run: pip install -r requirements.txt
- name: Run tests with coverage
run: |
python -m pytest steam_backlog_enforcer/tests/ \
--cov=steam_backlog_enforcer \
--cov-branch \
--cov-fail-under=100 \
-v

22
.gitignore vendored Normal file
View File

@ -0,0 +1,22 @@
__pycache__/
*.py[cod]
*$py.class
*.so
.Python
build/
dist/
*.egg-info/
.eggs/
.env
.venv/
venv/
ENV/
.pytest_cache/
.mypy_cache/
.ruff_cache/
coverage.xml
*.lcov
.coverage
htmlcov/
*.log
.DS_Store

164
.pre-commit-config.yaml Normal file
View File

@ -0,0 +1,164 @@
# Pre-commit Configuration for steam-backlog-enforcer
# Install: pre-commit install && pre-commit install --hook-type pre-push
# Run: pre-commit run --all-files
# Update: pre-commit autoupdate
default_language_version:
python: python3
default_stages: [pre-commit]
fail_fast: false
repos:
# ===========================================================================
# GENERAL HOOKS
# ===========================================================================
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.6.0
hooks:
- id: trailing-whitespace
args: [--markdown-linebreak-ext=md]
- id: end-of-file-fixer
- id: check-yaml
- id: check-json
- id: check-toml
- id: check-added-large-files
args: [--maxkb=2000]
- id: check-merge-conflict
- id: check-case-conflict
- id: detect-private-key
- id: debug-statements
- id: name-tests-test
args: [--pytest-test-first]
- id: check-ast
- id: check-builtin-literals
- id: check-docstring-first
- id: mixed-line-ending
args: [--fix=lf]
- id: requirements-txt-fixer
# ===========================================================================
# NOQA BLOCKER
# ===========================================================================
- repo: local
hooks:
- id: no-noqa
name: Block noqa comments
entry: '(?i)#\s*(noqa|type:\s*ignore)'
language: pygrep
types: [python]
- id: no-ruff-noqa
name: Block ruff noqa file-level comments
entry: '(?i)#\s*ruff:\s*noqa'
language: pygrep
types: [python]
# ===========================================================================
# RUFF - Linter + formatter
# ===========================================================================
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.15.2
hooks:
- id: ruff
args: [--fix, --unsafe-fixes, --exit-non-zero-on-fix, --show-fixes]
types_or: [python, pyi]
- id: ruff-format
types_or: [python, pyi]
# ===========================================================================
# MYPY - Type checking
# ===========================================================================
- repo: https://github.com/pre-commit/mirrors-mypy
rev: v1.13.0
hooks:
- id: mypy
args:
- --ignore-missing-imports
- --no-error-summary
- --disable-error-code=no-untyped-def
- --disable-error-code=no-untyped-call
- --disable-error-code=var-annotated
- --disable-error-code=no-any-unimported
- --disable-error-code=type-arg
- --disable-error-code=no-any-return
- --disable-error-code=misc
- --disable-error-code=unused-ignore
- --disable-error-code=unreachable
- --disable-error-code=assignment
- --disable-error-code=no-redef
- --disable-error-code=attr-defined
- --disable-error-code=arg-type
- --disable-error-code=union-attr
- --disable-error-code=call-overload
- --disable-error-code=return-value
- --disable-error-code=redundant-cast
- --disable-error-code=empty-body
- --disable-error-code=list-item
additional_dependencies:
- types-requests
# ===========================================================================
# PYLINT
# ===========================================================================
- repo: https://github.com/pylint-dev/pylint
rev: v3.3.2
hooks:
- id: pylint
args:
- --rcfile=pyproject.toml
- --fail-under=8.0
- --jobs=4
additional_dependencies:
- pytest
- requests
# ===========================================================================
# BANDIT - Security linter
# ===========================================================================
- repo: https://github.com/PyCQA/bandit
rev: 1.7.10
hooks:
- id: bandit
args:
- -c
- pyproject.toml
- --severity-level=high
- --confidence-level=medium
- --skip=B113
additional_dependencies: ["bandit[toml]"]
exclude: ^(tests/|.*test.*\.py$)
# ===========================================================================
# PYTEST + COVERAGE (push stage)
# ===========================================================================
- repo: local
hooks:
- id: pytest-coverage
name: pytest with coverage enforcement
entry: python -m pytest steam_backlog_enforcer/tests/ --cov=steam_backlog_enforcer --cov-branch --cov-fail-under=100
language: system
types: [python]
pass_filenames: false
require_serial: true
stages: [pre-push]
# ===========================================================================
# CODESPELL - Spell checking
# ===========================================================================
- repo: https://github.com/codespell-project/codespell
rev: v2.3.0
hooks:
- id: codespell
args:
- --skip=*.json,*.lock,.git,__pycache__,.venv
- --ignore-words-list=als,ans,ect,nd,som,sur,te,nam,numer,lew,sie,wil,postion,clen,ther,folow,derrive,ony,tje,noe,theses,crate,doubleclick,wile,tabel,pary,blok,bloc,proces,serwer,parametr,adres,hart,dout,metod,tekst,synonim,grup,mosty,lokal,skalar,milion,nowe,tre,hel,alph
# ===========================================================================
# SHELLCHECK - Shell script linting
# ===========================================================================
- repo: local
hooks:
- id: shellcheck
name: shellcheck
entry: bash -c 'printf "%s\0" "$@" | xargs -0 -n 40 shellcheck --severity=warning' --
language: system
types: [shell]

View File

@ -3,7 +3,6 @@
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO_ROOT="$(cd "$SCRIPT_DIR/../../.." && pwd)"
echo "=== Steam Backlog Enforcer Installer ==="
echo
@ -24,9 +23,9 @@ if [[ "${ans,,}" == "y" ]]; then
SERVICE_SRC="$SCRIPT_DIR/steam-backlog-enforcer.service"
SERVICE_DST="/etc/systemd/system/steam-backlog-enforcer.service"
# Set the correct working directory in the service file.
sed "s|WorkingDirectory=.*|WorkingDirectory=$REPO_ROOT|" "$SERVICE_SRC" \
> "$SERVICE_DST"
# Set the correct working directory and PYTHONPATH in the service file.
sed "s|WorkingDirectory=.*|WorkingDirectory=$SCRIPT_DIR|; s|PYTHONPATH=.*|PYTHONPATH=$SCRIPT_DIR|" \
"$SERVICE_SRC" > "$SERVICE_DST"
systemctl daemon-reload
systemctl enable steam-backlog-enforcer
@ -38,4 +37,4 @@ fi
echo
echo "Done! Run manually with:"
echo " sudo python3 -m python_pkg.steam_backlog_enforcer.main enforce"
echo " python3 -m steam_backlog_enforcer.main enforce"

166
pyproject.toml Normal file
View File

@ -0,0 +1,166 @@
[project]
name = "steam-backlog-enforcer"
version = "1.0.0"
description = "Enforce your Steam backlog: one game at a time, tracked with HowLongToBeat data"
requires-python = ">=3.10"
dependencies = [
"requests>=2.0",
"howlongtobeatpy>=1.0",
]
# ============================================================================
# RUFF - Fast Python linter and formatter
# ============================================================================
[tool.ruff]
target-version = "py310"
include = ["*.py", "**/*.py"]
exclude = [".git", ".venv", "__pycache__", "build", "dist", ".eggs"]
[tool.ruff.lint]
select = ["ALL"]
ignore = [
"D203", # conflicts with D211
"D213", # conflicts with D212
"COM812", # formatter handles this
"ISC001", # formatter may create these
"S603", # prone to false positives
]
fixable = ["ALL"]
unfixable = []
[tool.ruff.lint.per-file-ignores]
"**/tests/**/*.py" = ["ARG", "D", "PLC0415", "PLR2004", "S101", "SLF001"]
"**/test_*.py" = ["ARG", "D", "PLC0415", "PLR2004", "S101", "SLF001"]
[tool.ruff.lint.pydocstyle]
convention = "google"
[tool.ruff.lint.isort]
force-single-line = false
force-sort-within-sections = true
known-first-party = ["steam_backlog_enforcer"]
[tool.ruff.lint.flake8-quotes]
docstring-quotes = "double"
inline-quotes = "double"
[tool.ruff.lint.flake8-tidy-imports]
ban-relative-imports = "all"
[tool.ruff.format]
quote-style = "double"
indent-style = "space"
skip-magic-trailing-comma = false
line-ending = "auto"
docstring-code-format = true
# ============================================================================
# MYPY - Static type checker
# ============================================================================
[tool.mypy]
python_version = "3.10"
strict = true
warn_return_any = true
warn_unused_configs = true
disallow_untyped_defs = true
disallow_incomplete_defs = true
check_untyped_defs = true
disallow_untyped_decorators = true
no_implicit_optional = true
warn_redundant_casts = true
warn_unused_ignores = true
warn_no_return = true
warn_unreachable = true
disallow_any_unimported = true
disallow_any_explicit = false
disallow_any_generics = true
disallow_subclassing_any = true
strict_equality = true
extra_checks = true
ignore_missing_imports = true
show_error_codes = true
color_output = true
exclude = [".venv/"]
# ============================================================================
# PYLINT
# ============================================================================
[tool.pylint.main]
analyse-fallback-blocks = true
persistent = true
jobs = 0
py-version = "3.10"
ignore = [".venv", "__pycache__"]
ignore-patterns = [".*\\.pyi$"]
[tool.pylint.messages_control]
enable = "all"
disable = []
[tool.pylint.design]
min-public-methods = 0
max-module-lines = 1000
max-attributes = 10
[tool.pylint.typecheck]
generated-members = [
".*\\.assert_called_once_with",
".*\\.assert_called_once",
".*\\.assert_called",
".*\\.assert_not_called",
".*\\.assert_any_call",
".*\\.call_args",
".*\\.call_args_list",
".*\\.call_count",
]
# ============================================================================
# BANDIT - Security linter
# ============================================================================
[tool.bandit]
exclude_dirs = ["tests", ".venv"]
# ============================================================================
# PYTEST
# ============================================================================
[tool.pytest.ini_options]
testpaths = ["steam_backlog_enforcer/tests"]
python_files = ["test_*.py", "*_test.py"]
python_classes = ["Test*"]
python_functions = ["test_*"]
addopts = [
"-v",
"--strict-markers",
"--strict-config",
"-ra",
"--cov=steam_backlog_enforcer",
"--cov-branch",
"--cov-report=term-missing",
"--cov-report=lcov",
]
filterwarnings = [
"error",
"ignore::DeprecationWarning",
"default::pytest.PytestUnraisableExceptionWarning",
]
# ============================================================================
# COVERAGE
# ============================================================================
[tool.coverage.run]
source = ["steam_backlog_enforcer"]
branch = true
omit = ["*/__pycache__/*", "*/tests/*", "*/.venv/*"]
[tool.coverage.report]
fail_under = 100
show_missing = true
skip_covered = false
exclude_lines = [
"pragma: no cover",
"raise NotImplementedError",
"raise AssertionError",
"if TYPE_CHECKING:",
'if __name__ == "__main__":',
]
partial_branches = ["pragma: no branch"]

19
requirements.txt Normal file
View File

@ -0,0 +1,19 @@
# Steam Backlog Enforcer — runtime + development dependencies
# Install with: pip install -r requirements.txt
bandit>=1.7.0
codespell>=2.2.0
coverage>=7.4.0
howlongtobeatpy>=1.0
mypy>=1.8.0
pre-commit>=3.6.0
pylint>=3.0.0
pytest>=8.0.0
pytest-cov>=4.1.0
pytest-randomly>=3.15.0
pytest-sugar>=1.0.0
pytest-timeout>=2.2.0
pytest-xdist>=3.5.0
requests>=2.0
ruff>=0.8.0
types-requests>=2.31.0

View File

@ -3,5 +3,5 @@
# Usage: ./run.sh [command] (defaults to "done" if no command given)
set -euo pipefail
cd "$(dirname "$0")/../.."
exec python -m python_pkg.steam_backlog_enforcer.main "${1:-done}"
cd "$(dirname "$0")"
exec python -m steam_backlog_enforcer.main "${1:-done}"

View File

@ -5,12 +5,12 @@ Wants=network-online.target
[Service]
Type=simple
WorkingDirectory=/home/kuhy/testsAndMisc
ExecStart=/usr/bin/python3 -m python_pkg.steam_backlog_enforcer.main enforce
WorkingDirectory=/opt/steam-backlog-enforcer
ExecStart=/usr/bin/python3 -m steam_backlog_enforcer.main enforce
Restart=always
RestartSec=5
Environment=PYTHONUNBUFFERED=1
Environment=PYTHONPATH=/home/kuhy/testsAndMisc:/home/kuhy/.local/lib/python3.14/site-packages
Environment=PYTHONPATH=/opt/steam-backlog-enforcer
Environment=HOME=/home/kuhy
# Hardening: enforcer must not be easily killed.
OOMScoreAdjust=-900

View File

@ -5,28 +5,28 @@ from __future__ import annotations
import logging
import sys
from python_pkg.steam_backlog_enforcer._enforce_loop import get_all_owned_app_ids
from python_pkg.steam_backlog_enforcer.config import Config, State, load_snapshot
from python_pkg.steam_backlog_enforcer.enforcer import (
from steam_backlog_enforcer._enforce_loop import get_all_owned_app_ids
from steam_backlog_enforcer.config import Config, State, load_snapshot
from steam_backlog_enforcer.enforcer import (
enforce_allowed_game,
send_notification,
)
from python_pkg.steam_backlog_enforcer.game_install import (
from steam_backlog_enforcer.game_install import (
_echo,
install_game,
is_game_installed,
uninstall_other_games,
)
from python_pkg.steam_backlog_enforcer.hltb import (
from steam_backlog_enforcer.hltb import (
fetch_hltb_confidence_cached,
fetch_hltb_times_cached,
load_hltb_cache,
load_hltb_polls_cache,
save_hltb_cache,
)
from python_pkg.steam_backlog_enforcer.library_hider import hide_other_games
from python_pkg.steam_backlog_enforcer.scanning import pick_next_game
from python_pkg.steam_backlog_enforcer.steam_api import GameInfo, SteamAPIClient
from steam_backlog_enforcer.library_hider import hide_other_games
from steam_backlog_enforcer.scanning import pick_next_game
from steam_backlog_enforcer.steam_api import GameInfo, SteamAPIClient
_REASSIGN_REFRESH_LIMIT = 50
_SKIP_DAYS = 7

View File

@ -7,11 +7,11 @@ import logging
import time
from typing import Any
from python_pkg.steam_backlog_enforcer._whitelist import (
from steam_backlog_enforcer._whitelist import (
lock_enforcement_files,
promote_pending_exceptions,
)
from python_pkg.steam_backlog_enforcer.config import (
from steam_backlog_enforcer.config import (
CONFIG_DIR,
CONFIG_FILE,
Config,
@ -19,11 +19,11 @@ from python_pkg.steam_backlog_enforcer.config import (
_atomic_write,
load_snapshot,
)
from python_pkg.steam_backlog_enforcer.enforcer import (
from steam_backlog_enforcer.enforcer import (
enforce_allowed_game,
send_notification,
)
from python_pkg.steam_backlog_enforcer.game_install import (
from steam_backlog_enforcer.game_install import (
_echo,
get_installed_games,
install_game,
@ -32,9 +32,9 @@ from python_pkg.steam_backlog_enforcer.game_install import (
uninstall_game,
uninstall_other_games,
)
from python_pkg.steam_backlog_enforcer.library_hider import hide_other_games
from python_pkg.steam_backlog_enforcer.steam_api import SteamAPIClient
from python_pkg.steam_backlog_enforcer.store_blocker import block_store
from steam_backlog_enforcer.library_hider import hide_other_games
from steam_backlog_enforcer.steam_api import SteamAPIClient
from steam_backlog_enforcer.store_blocker import block_store
logger = logging.getLogger(__name__)
_OWNED_IDS_CACHE_FILE = CONFIG_DIR / "owned_app_ids_cache.json"

View File

@ -11,7 +11,7 @@ from typing import Any
import aiohttp
from python_pkg.steam_backlog_enforcer._hltb_types import (
from steam_backlog_enforcer._hltb_types import (
_SAVE_INTERVAL,
HLTB_BASE_URL,
MAX_CONCURRENT,

View File

@ -15,10 +15,10 @@ from typing import Any
import aiohttp
from howlongtobeatpy.HTMLRequests import HTMLRequests
from python_pkg.steam_backlog_enforcer._hltb_detail import (
from steam_backlog_enforcer._hltb_detail import (
_fetch_leisure_times,
)
from python_pkg.steam_backlog_enforcer._hltb_types import (
from steam_backlog_enforcer._hltb_types import (
_SAVE_INTERVAL,
_SUBSET_SUFFIXES,
MAX_CONCURRENT,

View File

@ -8,7 +8,7 @@ import json
import logging
from typing import Any
from python_pkg.steam_backlog_enforcer.config import CONFIG_DIR, _atomic_write
from steam_backlog_enforcer.config import CONFIG_DIR, _atomic_write
logger = logging.getLogger(__name__)

View File

@ -5,19 +5,19 @@ from __future__ import annotations
import logging
from typing import TYPE_CHECKING
from python_pkg.steam_backlog_enforcer._hltb_types import (
from steam_backlog_enforcer._hltb_types import (
_HLTBExtras,
load_hltb_cache,
load_hltb_count_comp_cache,
load_hltb_polls_cache,
save_hltb_cache,
)
from python_pkg.steam_backlog_enforcer.game_install import _echo
from python_pkg.steam_backlog_enforcer.hltb import fetch_hltb_confidence_cached
from steam_backlog_enforcer.game_install import _echo
from steam_backlog_enforcer.hltb import fetch_hltb_confidence_cached
if TYPE_CHECKING:
from python_pkg.steam_backlog_enforcer.config import State
from python_pkg.steam_backlog_enforcer.steam_api import GameInfo
from steam_backlog_enforcer.config import State
from steam_backlog_enforcer.steam_api import GameInfo
logger = logging.getLogger(__name__)

View File

@ -9,29 +9,29 @@ import secrets
from typing import TYPE_CHECKING
from urllib.parse import quote_plus
from python_pkg.steam_backlog_enforcer._hltb_types import (
from steam_backlog_enforcer._hltb_types import (
HLTB_BASE_URL,
load_hltb_cache,
load_hltb_game_id_cache,
load_hltb_leisure_100h_cache,
load_hltb_rush_cache,
)
from python_pkg.steam_backlog_enforcer._scanning_confidence import (
from steam_backlog_enforcer._scanning_confidence import (
_apply_cached_confidence_to_candidates,
_confidence_fail_reasons,
_refresh_candidate_confidence_batch,
)
from python_pkg.steam_backlog_enforcer.config import load_snapshot
from python_pkg.steam_backlog_enforcer.game_install import _echo
from python_pkg.steam_backlog_enforcer.hltb import fetch_hltb_detail_missing
from python_pkg.steam_backlog_enforcer.protondb import (
from steam_backlog_enforcer.config import load_snapshot
from steam_backlog_enforcer.game_install import _echo
from steam_backlog_enforcer.hltb import fetch_hltb_detail_missing
from steam_backlog_enforcer.protondb import (
ProtonDBRating,
fetch_protondb_ratings,
)
from python_pkg.steam_backlog_enforcer.steam_api import GameInfo
from steam_backlog_enforcer.steam_api import GameInfo
if TYPE_CHECKING:
from python_pkg.steam_backlog_enforcer.config import Config, State
from steam_backlog_enforcer.config import Config, State
logger = logging.getLogger(__name__)

View File

@ -13,7 +13,7 @@ import subprocess
import time
from typing import TYPE_CHECKING, cast
from python_pkg.steam_backlog_enforcer.config import CONFIG_DIR, _atomic_write
from steam_backlog_enforcer.config import CONFIG_DIR, _atomic_write
if TYPE_CHECKING:
from pathlib import Path

View File

@ -9,7 +9,7 @@ import shutil
import signal
import subprocess
from python_pkg.steam_backlog_enforcer.game_install import (
from steam_backlog_enforcer.game_install import (
is_protected_app,
)

View File

@ -13,7 +13,7 @@ import subprocess
import sys
import time
from python_pkg.steam_backlog_enforcer._whitelist import get_approved_exception_ids
from steam_backlog_enforcer._whitelist import get_approved_exception_ids
logger = logging.getLogger(__name__)

View File

@ -18,14 +18,14 @@ import time
import aiohttp
from python_pkg.steam_backlog_enforcer._hltb_search import (
from steam_backlog_enforcer._hltb_search import (
_fetch_batch,
_get_auth_info,
_get_hltb_search_url,
_search_one,
_SearchCtx,
)
from python_pkg.steam_backlog_enforcer._hltb_types import (
from steam_backlog_enforcer._hltb_types import (
HLTB_BASE_URL,
MAX_CONCURRENT,
HLTBResult,

View File

@ -7,26 +7,26 @@ import sys
import time
from typing import TYPE_CHECKING
from python_pkg.steam_backlog_enforcer._cmd_done import cmd_done
from python_pkg.steam_backlog_enforcer._enforce_loop import (
from steam_backlog_enforcer._cmd_done import cmd_done
from steam_backlog_enforcer._enforce_loop import (
do_enforce,
get_all_owned_app_ids,
)
from python_pkg.steam_backlog_enforcer._hltb_types import load_hltb_cache
from python_pkg.steam_backlog_enforcer._stats import cmd_stats
from python_pkg.steam_backlog_enforcer._whitelist import (
from steam_backlog_enforcer._hltb_types import load_hltb_cache
from steam_backlog_enforcer._stats import cmd_stats
from steam_backlog_enforcer._whitelist import (
WHITELIST_COOLDOWN_SECONDS,
add_pending_exception,
list_pending_exceptions,
validate_reason,
)
from python_pkg.steam_backlog_enforcer.config import (
from steam_backlog_enforcer.config import (
Config,
State,
interactive_setup,
load_snapshot,
)
from python_pkg.steam_backlog_enforcer.game_install import (
from steam_backlog_enforcer.game_install import (
_echo,
get_installed_games,
install_game,
@ -34,18 +34,18 @@ from python_pkg.steam_backlog_enforcer.game_install import (
is_protected_app,
uninstall_other_games,
)
from python_pkg.steam_backlog_enforcer.library_hider import (
from steam_backlog_enforcer.library_hider import (
hide_other_games,
restart_steam,
unhide_all_games,
)
from python_pkg.steam_backlog_enforcer.scanning import (
from steam_backlog_enforcer.scanning import (
do_check,
do_scan,
pick_next_game,
)
from python_pkg.steam_backlog_enforcer.steam_api import GameInfo
from python_pkg.steam_backlog_enforcer.store_blocker import (
from steam_backlog_enforcer.steam_api import GameInfo
from steam_backlog_enforcer.store_blocker import (
block_store,
is_store_blocked,
unblock_store,
@ -415,7 +415,7 @@ def main() -> None:
"""CLI entry point."""
if len(sys.argv) < _MIN_CLI_ARGS or sys.argv[1] not in _ALL_COMMANDS:
_echo("Steam Backlog Enforcer\n")
_echo("Usage: python -m python_pkg.steam_backlog_enforcer.main <command>\n")
_echo("Usage: python -m steam_backlog_enforcer.main <command>\n")
_echo("Commands:")
for name, desc in _ALL_COMMANDS.items():
_echo(f" {name:<14s} {desc}")

View File

@ -17,7 +17,7 @@ from typing import Any
import aiohttp
from python_pkg.steam_backlog_enforcer.config import CONFIG_DIR, _atomic_write
from steam_backlog_enforcer.config import CONFIG_DIR, _atomic_write
logger = logging.getLogger(__name__)

View File

@ -7,38 +7,38 @@ import logging
import time
from typing import TYPE_CHECKING, Any
from python_pkg.steam_backlog_enforcer._hltb_types import (
from steam_backlog_enforcer._hltb_types import (
load_hltb_count_comp_cache,
load_hltb_polls_cache,
)
from python_pkg.steam_backlog_enforcer._scanning_confidence import (
from steam_backlog_enforcer._scanning_confidence import (
_apply_cached_confidence_to_candidates,
_candidate_passes_hltb_confidence,
_report_poll_confidence,
)
from python_pkg.steam_backlog_enforcer.config import (
from steam_backlog_enforcer.config import (
Config,
State,
load_snapshot,
save_snapshot,
)
from python_pkg.steam_backlog_enforcer.enforcer import (
from steam_backlog_enforcer.enforcer import (
send_notification,
)
from python_pkg.steam_backlog_enforcer.game_install import (
from steam_backlog_enforcer.game_install import (
_echo,
install_game,
is_game_installed,
uninstall_other_games,
)
from python_pkg.steam_backlog_enforcer.hltb import (
from steam_backlog_enforcer.hltb import (
fetch_hltb_times_cached,
)
from python_pkg.steam_backlog_enforcer.protondb import (
from steam_backlog_enforcer.protondb import (
ProtonDBRating,
fetch_protondb_ratings,
)
from python_pkg.steam_backlog_enforcer.steam_api import GameInfo, SteamAPIClient
from steam_backlog_enforcer.steam_api import GameInfo, SteamAPIClient
if TYPE_CHECKING:
from collections.abc import Callable

View File

@ -21,7 +21,7 @@ import shutil
import socket
import subprocess
from python_pkg.steam_backlog_enforcer.config import (
from steam_backlog_enforcer.config import (
BLOCKED_DOMAINS,
HOSTS_FILE,
)

View File

@ -39,58 +39,58 @@ def _isolate_filesystem(tmp_path: Path) -> Iterator[None]:
with (
# Config / state / snapshot paths (used by State.save, Config.save, etc.)
patch(
"python_pkg.steam_backlog_enforcer.config.CONFIG_DIR",
"steam_backlog_enforcer.config.CONFIG_DIR",
fake_config,
),
patch(
"python_pkg.steam_backlog_enforcer.config.CONFIG_FILE",
"steam_backlog_enforcer.config.CONFIG_FILE",
fake_config / "config.json",
),
patch(
"python_pkg.steam_backlog_enforcer.config.STATE_FILE",
"steam_backlog_enforcer.config.STATE_FILE",
fake_config / "state.json",
),
patch(
"python_pkg.steam_backlog_enforcer.config.SNAPSHOT_FILE",
"steam_backlog_enforcer.config.SNAPSHOT_FILE",
fake_config / "snapshot.json",
),
# Steam game manifests / install dirs
patch(
"python_pkg.steam_backlog_enforcer.game_install.STEAMAPPS_PATH",
"steam_backlog_enforcer.game_install.STEAMAPPS_PATH",
fake_steamapps,
),
# HLTB cache file (computed at import time from CONFIG_DIR, so
# patching CONFIG_DIR alone does not redirect it)
patch(
"python_pkg.steam_backlog_enforcer._hltb_types.HLTB_CACHE_FILE",
"steam_backlog_enforcer._hltb_types.HLTB_CACHE_FILE",
fake_config / "hltb_cache.json",
),
# /etc/hosts (store blocker)
patch(
"python_pkg.steam_backlog_enforcer.store_blocker.HOSTS_FILE",
"steam_backlog_enforcer.store_blocker.HOSTS_FILE",
fake_hosts,
),
patch(
"python_pkg.steam_backlog_enforcer.config.HOSTS_FILE",
"steam_backlog_enforcer.config.HOSTS_FILE",
fake_hosts,
),
# Whitelist exception files (_whitelist module-level constants)
patch(
"python_pkg.steam_backlog_enforcer._whitelist.PENDING_EXCEPTIONS_FILE",
"steam_backlog_enforcer._whitelist.PENDING_EXCEPTIONS_FILE",
fake_config / "pending_exceptions.json",
),
patch(
"python_pkg.steam_backlog_enforcer._whitelist.APPROVED_EXCEPTIONS_FILE",
"steam_backlog_enforcer._whitelist.APPROVED_EXCEPTIONS_FILE",
fake_config / "approved_exceptions.json",
),
patch(
"python_pkg.steam_backlog_enforcer._whitelist.EXCEPTION_AUDIT_LOG",
"steam_backlog_enforcer._whitelist.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(
"python_pkg.steam_backlog_enforcer._enforce_loop.CONFIG_FILE",
"steam_backlog_enforcer._enforce_loop.CONFIG_FILE",
fake_config / "config.json",
),
):
@ -110,27 +110,27 @@ def _block_real_subprocesses() -> Iterator[None]:
with (
patch(
"python_pkg.steam_backlog_enforcer.game_install.subprocess.run",
"steam_backlog_enforcer.game_install.subprocess.run",
noop_run,
),
patch(
"python_pkg.steam_backlog_enforcer.game_install.subprocess.Popen",
"steam_backlog_enforcer.game_install.subprocess.Popen",
noop_popen,
),
patch(
"python_pkg.steam_backlog_enforcer.enforcer.subprocess.run",
"steam_backlog_enforcer.enforcer.subprocess.run",
noop_run,
),
patch(
"python_pkg.steam_backlog_enforcer.store_blocker.subprocess.run",
"steam_backlog_enforcer.store_blocker.subprocess.run",
noop_run,
),
patch(
"python_pkg.steam_backlog_enforcer.library_hider.subprocess.run",
"steam_backlog_enforcer.library_hider.subprocess.run",
noop_run,
),
patch(
"python_pkg.steam_backlog_enforcer.library_hider.subprocess.Popen",
"steam_backlog_enforcer.library_hider.subprocess.Popen",
noop_popen,
),
):
@ -147,9 +147,9 @@ def _no_real_sleep() -> Iterator[None]:
"""
noop = MagicMock()
with (
patch("python_pkg.steam_backlog_enforcer.game_install.time.sleep", noop),
patch("python_pkg.steam_backlog_enforcer.library_hider.time.sleep", noop),
patch("python_pkg.steam_backlog_enforcer.steam_api.time.sleep", noop),
patch("python_pkg.steam_backlog_enforcer._enforce_loop.time.sleep", noop),
patch("steam_backlog_enforcer.game_install.time.sleep", noop),
patch("steam_backlog_enforcer.library_hider.time.sleep", noop),
patch("steam_backlog_enforcer.steam_api.time.sleep", noop),
patch("steam_backlog_enforcer._enforce_loop.time.sleep", noop),
):
yield

View File

@ -4,10 +4,10 @@ from __future__ import annotations
from unittest.mock import patch
from python_pkg.steam_backlog_enforcer._cmd_done import _prompt_keep_or_skip
from python_pkg.steam_backlog_enforcer.steam_api import GameInfo
from steam_backlog_enforcer._cmd_done import _prompt_keep_or_skip
from steam_backlog_enforcer.steam_api import GameInfo
CMD_DONE_PKG = "python_pkg.steam_backlog_enforcer._cmd_done"
CMD_DONE_PKG = "steam_backlog_enforcer._cmd_done"
class TestPromptKeepOrSkip:

View File

@ -8,7 +8,7 @@ from unittest.mock import patch
import pytest
from python_pkg.steam_backlog_enforcer.config import (
from steam_backlog_enforcer.config import (
Config,
State,
_atomic_write,
@ -38,7 +38,7 @@ class TestAtomicWrite:
target = tmp_path / "out.json"
with (
patch(
"python_pkg.steam_backlog_enforcer.config.os.write",
"steam_backlog_enforcer.config.os.write",
side_effect=OSError("disk full"),
),
pytest.raises(OSError, match="disk full"),
@ -81,8 +81,8 @@ class TestConfig:
config_dir = tmp_path / "cfg"
config_file = config_dir / "config.json"
with (
patch("python_pkg.steam_backlog_enforcer.config.CONFIG_DIR", config_dir),
patch("python_pkg.steam_backlog_enforcer.config.CONFIG_FILE", config_file),
patch("steam_backlog_enforcer.config.CONFIG_DIR", config_dir),
patch("steam_backlog_enforcer.config.CONFIG_FILE", config_file),
):
cfg.save()
data = json.loads(config_file.read_text(encoding="utf-8"))
@ -95,14 +95,14 @@ class TestConfig:
json.dumps({"steam_api_key": "key1", "steam_id": "id1"}) + "\n",
encoding="utf-8",
)
with patch("python_pkg.steam_backlog_enforcer.config.CONFIG_FILE", config_file):
with patch("steam_backlog_enforcer.config.CONFIG_FILE", config_file):
cfg = Config.load()
assert cfg.steam_api_key == "key1"
assert cfg.steam_id == "id1"
def test_load_missing(self, tmp_path: Path) -> None:
config_file = tmp_path / "nonexistent.json"
with patch("python_pkg.steam_backlog_enforcer.config.CONFIG_FILE", config_file):
with patch("steam_backlog_enforcer.config.CONFIG_FILE", config_file):
cfg = Config.load()
assert cfg.steam_api_key == ""
@ -112,7 +112,7 @@ class TestConfig:
json.dumps({"steam_api_key": "k", "unknown_field": 42}) + "\n",
encoding="utf-8",
)
with patch("python_pkg.steam_backlog_enforcer.config.CONFIG_FILE", config_file):
with patch("steam_backlog_enforcer.config.CONFIG_FILE", config_file):
cfg = Config.load()
assert cfg.steam_api_key == "k"
@ -131,8 +131,8 @@ class TestState:
config_dir = tmp_path / "cfg"
state_file = config_dir / "state.json"
with (
patch("python_pkg.steam_backlog_enforcer.config.CONFIG_DIR", config_dir),
patch("python_pkg.steam_backlog_enforcer.config.STATE_FILE", state_file),
patch("steam_backlog_enforcer.config.CONFIG_DIR", config_dir),
patch("steam_backlog_enforcer.config.STATE_FILE", state_file),
):
state.save()
data = json.loads(state_file.read_text(encoding="utf-8"))
@ -152,21 +152,21 @@ class TestState:
+ "\n",
encoding="utf-8",
)
with patch("python_pkg.steam_backlog_enforcer.config.STATE_FILE", state_file):
with patch("steam_backlog_enforcer.config.STATE_FILE", state_file):
st = State.load()
assert st.current_app_id == 50
assert st.finished_app_ids == [1, 2]
def test_load_missing(self, tmp_path: Path) -> None:
state_file = tmp_path / "nonexistent.json"
with patch("python_pkg.steam_backlog_enforcer.config.STATE_FILE", state_file):
with patch("steam_backlog_enforcer.config.STATE_FILE", state_file):
st = State.load()
assert st.current_app_id is None
def test_load_corrupt(self, tmp_path: Path) -> None:
state_file = tmp_path / "state.json"
state_file.write_text("not valid json{{", encoding="utf-8")
with patch("python_pkg.steam_backlog_enforcer.config.STATE_FILE", state_file):
with patch("steam_backlog_enforcer.config.STATE_FILE", state_file):
st = State.load()
assert st.current_app_id is None
assert st.current_game_name == ""
@ -215,8 +215,8 @@ class TestSnapshot:
config_dir = tmp_path / "cfg"
snap_file = config_dir / "snapshot.json"
with (
patch("python_pkg.steam_backlog_enforcer.config.CONFIG_DIR", config_dir),
patch("python_pkg.steam_backlog_enforcer.config.SNAPSHOT_FILE", snap_file),
patch("steam_backlog_enforcer.config.CONFIG_DIR", config_dir),
patch("steam_backlog_enforcer.config.SNAPSHOT_FILE", snap_file),
):
data: list[dict[str, Any]] = [{"app_id": 1, "name": "G1"}]
save_snapshot(data)
@ -225,7 +225,7 @@ class TestSnapshot:
def test_load_none(self, tmp_path: Path) -> None:
snap_file = tmp_path / "nonexistent.json"
with patch("python_pkg.steam_backlog_enforcer.config.SNAPSHOT_FILE", snap_file):
with patch("steam_backlog_enforcer.config.SNAPSHOT_FILE", snap_file):
assert load_snapshot() is None
@ -236,8 +236,8 @@ class TestInteractiveSetup:
config_dir = tmp_path / "cfg"
config_file = config_dir / "config.json"
with (
patch("python_pkg.steam_backlog_enforcer.config.CONFIG_DIR", config_dir),
patch("python_pkg.steam_backlog_enforcer.config.CONFIG_FILE", config_file),
patch("steam_backlog_enforcer.config.CONFIG_DIR", config_dir),
patch("steam_backlog_enforcer.config.CONFIG_FILE", config_file),
patch("builtins.input", side_effect=["mykey", "myid"]),
):
cfg = interactive_setup()
@ -256,8 +256,8 @@ class TestInteractiveSetup:
config_dir = tmp_path / "cfg"
config_file = config_dir / "config.json"
with (
patch("python_pkg.steam_backlog_enforcer.config.CONFIG_DIR", config_dir),
patch("python_pkg.steam_backlog_enforcer.config.CONFIG_FILE", config_file),
patch("steam_backlog_enforcer.config.CONFIG_DIR", config_dir),
patch("steam_backlog_enforcer.config.CONFIG_FILE", config_file),
patch("builtins.input", side_effect=["key", ""]),
pytest.raises(SystemExit),
):

View File

@ -6,7 +6,7 @@ import json
from typing import TYPE_CHECKING
from unittest.mock import MagicMock, patch
from python_pkg.steam_backlog_enforcer._enforce_loop import (
from steam_backlog_enforcer._enforce_loop import (
_enforce_auto_install,
_enforce_hide_games,
_enforce_setup,
@ -15,12 +15,12 @@ from python_pkg.steam_backlog_enforcer._enforce_loop import (
_save_owned_app_ids_cache,
get_all_owned_app_ids,
)
from python_pkg.steam_backlog_enforcer.config import Config, State
from steam_backlog_enforcer.config import Config, State
if TYPE_CHECKING:
from pathlib import Path
PKG = "python_pkg.steam_backlog_enforcer._enforce_loop"
PKG = "steam_backlog_enforcer._enforce_loop"
class TestGetAllOwnedAppIds:

View File

@ -4,13 +4,13 @@ from __future__ import annotations
from unittest.mock import patch
from python_pkg.steam_backlog_enforcer._enforce_loop import (
from steam_backlog_enforcer._enforce_loop import (
_enforce_loop_iteration,
do_enforce,
)
from python_pkg.steam_backlog_enforcer.config import Config, State
from steam_backlog_enforcer.config import Config, State
PKG = "python_pkg.steam_backlog_enforcer._enforce_loop"
PKG = "steam_backlog_enforcer._enforce_loop"
class TestEnforceLoopIteration:

View File

@ -5,7 +5,7 @@ from __future__ import annotations
from typing import TYPE_CHECKING
from unittest.mock import patch
from python_pkg.steam_backlog_enforcer.enforcer import (
from steam_backlog_enforcer.enforcer import (
enforce_allowed_game,
get_running_steam_game_pids,
kill_process,
@ -27,7 +27,7 @@ class TestGetRunningPids:
(pid_dir / "environ").write_bytes(environ)
with patch(
"python_pkg.steam_backlog_enforcer.enforcer.Path",
"steam_backlog_enforcer.enforcer.Path",
return_value=proc_dir,
):
result = get_running_steam_game_pids()
@ -40,7 +40,7 @@ class TestGetRunningPids:
(proc_dir / "cpuinfo").touch()
with patch(
"python_pkg.steam_backlog_enforcer.enforcer.Path",
"steam_backlog_enforcer.enforcer.Path",
return_value=proc_dir,
):
result = get_running_steam_game_pids()
@ -53,7 +53,7 @@ class TestGetRunningPids:
# No environ file -> OSError when reading
with patch(
"python_pkg.steam_backlog_enforcer.enforcer.Path",
"steam_backlog_enforcer.enforcer.Path",
return_value=proc_dir,
):
result = get_running_steam_game_pids()
@ -67,7 +67,7 @@ class TestGetRunningPids:
(pid_dir / "environ").write_bytes(environ)
with patch(
"python_pkg.steam_backlog_enforcer.enforcer.Path",
"steam_backlog_enforcer.enforcer.Path",
return_value=proc_dir,
):
result = get_running_steam_game_pids()
@ -81,7 +81,7 @@ class TestGetRunningPids:
(pid_dir / "environ").write_bytes(environ)
with patch(
"python_pkg.steam_backlog_enforcer.enforcer.Path",
"steam_backlog_enforcer.enforcer.Path",
return_value=proc_dir,
):
result = get_running_steam_game_pids()
@ -93,7 +93,7 @@ class TestEnforceAllowedGame:
def test_no_violations(self) -> None:
with patch(
"python_pkg.steam_backlog_enforcer.enforcer.get_running_steam_game_pids",
"steam_backlog_enforcer.enforcer.get_running_steam_game_pids",
return_value={100: 440},
):
result = enforce_allowed_game(440)
@ -102,11 +102,11 @@ class TestEnforceAllowedGame:
def test_kills_unauthorized(self) -> None:
with (
patch(
"python_pkg.steam_backlog_enforcer.enforcer.get_running_steam_game_pids",
"steam_backlog_enforcer.enforcer.get_running_steam_game_pids",
return_value={100: 570, 200: 440},
),
patch(
"python_pkg.steam_backlog_enforcer.enforcer.kill_process"
"steam_backlog_enforcer.enforcer.kill_process"
) as mock_kill,
):
result = enforce_allowed_game(440, kill_unauthorized=True)
@ -115,7 +115,7 @@ class TestEnforceAllowedGame:
def test_skips_app_id_zero(self) -> None:
with patch(
"python_pkg.steam_backlog_enforcer.enforcer.get_running_steam_game_pids",
"steam_backlog_enforcer.enforcer.get_running_steam_game_pids",
return_value={100: 0},
):
result = enforce_allowed_game(440)
@ -123,7 +123,7 @@ class TestEnforceAllowedGame:
def test_detects_without_killing(self) -> None:
with patch(
"python_pkg.steam_backlog_enforcer.enforcer.get_running_steam_game_pids",
"steam_backlog_enforcer.enforcer.get_running_steam_game_pids",
return_value={100: 570},
):
result = enforce_allowed_game(440, kill_unauthorized=False)
@ -137,15 +137,15 @@ class TestEnforceAllowedGame:
"""Protected IDs must never be killed even if not the assigned game."""
with (
patch(
"python_pkg.steam_backlog_enforcer.enforcer.get_running_steam_game_pids",
"steam_backlog_enforcer.enforcer.get_running_steam_game_pids",
return_value={100: 1331550, 200: 440},
),
patch(
"python_pkg.steam_backlog_enforcer.enforcer.is_protected_app",
"steam_backlog_enforcer.enforcer.is_protected_app",
side_effect=lambda aid: aid == 1331550,
),
patch(
"python_pkg.steam_backlog_enforcer.enforcer.kill_process"
"steam_backlog_enforcer.enforcer.kill_process"
) as mock_kill,
):
result = enforce_allowed_game(440, kill_unauthorized=True)
@ -157,20 +157,20 @@ class TestKillProcess:
"""Tests for kill_process."""
def test_kill_success(self) -> None:
with patch("python_pkg.steam_backlog_enforcer.enforcer.os.kill") as mock_kill:
with patch("steam_backlog_enforcer.enforcer.os.kill") as mock_kill:
kill_process(123, 440)
mock_kill.assert_called_once()
def test_process_already_gone(self) -> None:
with patch(
"python_pkg.steam_backlog_enforcer.enforcer.os.kill",
"steam_backlog_enforcer.enforcer.os.kill",
side_effect=ProcessLookupError,
):
kill_process(123, 440) # Should not raise
def test_permission_error(self) -> None:
with patch(
"python_pkg.steam_backlog_enforcer.enforcer.os.kill",
"steam_backlog_enforcer.enforcer.os.kill",
side_effect=PermissionError,
):
kill_process(123, 440) # Should not raise
@ -181,21 +181,21 @@ class TestSendNotification:
def test_sends(self) -> None:
with patch(
"python_pkg.steam_backlog_enforcer.enforcer.subprocess.run"
"steam_backlog_enforcer.enforcer.subprocess.run"
) as mock_run:
send_notification("Title", "Body")
mock_run.assert_called_once()
def test_handles_missing_notify_send(self) -> None:
with patch(
"python_pkg.steam_backlog_enforcer.enforcer.subprocess.run",
"steam_backlog_enforcer.enforcer.subprocess.run",
side_effect=FileNotFoundError,
):
send_notification("Title", "Body") # Should not raise
def test_handles_os_error(self) -> None:
with patch(
"python_pkg.steam_backlog_enforcer.enforcer.subprocess.run",
"steam_backlog_enforcer.enforcer.subprocess.run",
side_effect=OSError,
):
send_notification("Title", "Body") # Should not raise

View File

@ -8,7 +8,7 @@ from unittest.mock import MagicMock, patch
import pytest
from python_pkg.steam_backlog_enforcer.game_install import (
from steam_backlog_enforcer.game_install import (
_assert_not_real_steam,
_echo,
_ensure_steam_running,
@ -22,7 +22,7 @@ if TYPE_CHECKING:
from pathlib import Path
PKG = "python_pkg.steam_backlog_enforcer.game_install"
PKG = "steam_backlog_enforcer.game_install"
class TestAssertNotRealSteam:
@ -99,7 +99,7 @@ class TestTriggerSteamInstall:
def test_success(self) -> None:
with patch(
"python_pkg.steam_backlog_enforcer.game_install.subprocess.run"
"steam_backlog_enforcer.game_install.subprocess.run"
) as mock_run:
result = _trigger_steam_install(440, "TF2")
assert result is True
@ -107,7 +107,7 @@ class TestTriggerSteamInstall:
def test_file_not_found(self) -> None:
with patch(
"python_pkg.steam_backlog_enforcer.game_install.subprocess.run",
"steam_backlog_enforcer.game_install.subprocess.run",
side_effect=FileNotFoundError,
):
result = _trigger_steam_install(440, "TF2")
@ -115,7 +115,7 @@ class TestTriggerSteamInstall:
def test_os_error(self) -> None:
with patch(
"python_pkg.steam_backlog_enforcer.game_install.subprocess.run",
"steam_backlog_enforcer.game_install.subprocess.run",
side_effect=OSError,
):
result = _trigger_steam_install(440, "TF2")
@ -125,7 +125,7 @@ class TestTriggerSteamInstall:
import subprocess
with patch(
"python_pkg.steam_backlog_enforcer.game_install.subprocess.run",
"steam_backlog_enforcer.game_install.subprocess.run",
side_effect=subprocess.TimeoutExpired("cmd", 15),
):
result = _trigger_steam_install(440, "TF2")
@ -155,14 +155,14 @@ class TestGetUidGid:
mock_pw.pw_uid = 1001
mock_pw.pw_gid = 1001
with patch(
"python_pkg.steam_backlog_enforcer.game_install.pwd.getpwnam",
"steam_backlog_enforcer.game_install.pwd.getpwnam",
return_value=mock_pw,
):
assert _get_uid_gid_for_user("alice") == (1001, 1001)
def test_unknown_user(self) -> None:
with patch(
"python_pkg.steam_backlog_enforcer.game_install.pwd.getpwnam",
"steam_backlog_enforcer.game_install.pwd.getpwnam",
side_effect=KeyError,
):
assert _get_uid_gid_for_user("nobody") == (1000, 1000)
@ -175,13 +175,13 @@ class TestIsGameInstalled:
manifest = tmp_path / "appmanifest_440.acf"
manifest.touch()
with patch(
"python_pkg.steam_backlog_enforcer.game_install.STEAMAPPS_PATH", tmp_path
"steam_backlog_enforcer.game_install.STEAMAPPS_PATH", tmp_path
):
assert is_game_installed(440) is True
def test_not_installed(self, tmp_path: Path) -> None:
with patch(
"python_pkg.steam_backlog_enforcer.game_install.STEAMAPPS_PATH", tmp_path
"steam_backlog_enforcer.game_install.STEAMAPPS_PATH", tmp_path
):
assert is_game_installed(440) is False
@ -192,7 +192,7 @@ class TestEnsureSteamRunning:
def test_already_running(self) -> None:
mock_result = MagicMock(returncode=0)
with patch(
"python_pkg.steam_backlog_enforcer.game_install.subprocess.run",
"steam_backlog_enforcer.game_install.subprocess.run",
return_value=mock_result,
):
_ensure_steam_running()
@ -201,17 +201,17 @@ class TestEnsureSteamRunning:
mock_result = MagicMock(returncode=1)
with (
patch(
"python_pkg.steam_backlog_enforcer.game_install.subprocess.run",
"steam_backlog_enforcer.game_install.subprocess.run",
return_value=mock_result,
),
patch(
"python_pkg.steam_backlog_enforcer.game_install.subprocess.Popen"
"steam_backlog_enforcer.game_install.subprocess.Popen"
) as mock_popen,
patch(
"python_pkg.steam_backlog_enforcer.game_install.os.geteuid",
"steam_backlog_enforcer.game_install.os.geteuid",
return_value=1000,
),
patch("python_pkg.steam_backlog_enforcer.game_install.time.sleep"),
patch("steam_backlog_enforcer.game_install.time.sleep"),
):
_ensure_steam_running()
mock_popen.assert_called_once()
@ -223,25 +223,25 @@ class TestEnsureSteamRunning:
mock_pw.pw_gid = 1000
with (
patch(
"python_pkg.steam_backlog_enforcer.game_install.subprocess.run",
"steam_backlog_enforcer.game_install.subprocess.run",
return_value=mock_result,
),
patch(
"python_pkg.steam_backlog_enforcer.game_install.subprocess.Popen"
"steam_backlog_enforcer.game_install.subprocess.Popen"
) as mock_popen,
patch(
"python_pkg.steam_backlog_enforcer.game_install.os.geteuid",
"steam_backlog_enforcer.game_install.os.geteuid",
return_value=0,
),
patch(
"python_pkg.steam_backlog_enforcer.game_install._get_real_user",
"steam_backlog_enforcer.game_install._get_real_user",
return_value="alice",
),
patch(
"python_pkg.steam_backlog_enforcer.game_install._get_uid_gid_for_user",
"steam_backlog_enforcer.game_install._get_uid_gid_for_user",
return_value=(1000, 1000),
),
patch("python_pkg.steam_backlog_enforcer.game_install.time.sleep"),
patch("steam_backlog_enforcer.game_install.time.sleep"),
):
_ensure_steam_running()
mock_popen.assert_called_once()
@ -249,15 +249,15 @@ class TestEnsureSteamRunning:
def test_pgrep_not_found(self) -> None:
with (
patch(
"python_pkg.steam_backlog_enforcer.game_install.subprocess.run",
"steam_backlog_enforcer.game_install.subprocess.run",
side_effect=FileNotFoundError,
),
patch("python_pkg.steam_backlog_enforcer.game_install.subprocess.Popen"),
patch("steam_backlog_enforcer.game_install.subprocess.Popen"),
patch(
"python_pkg.steam_backlog_enforcer.game_install.os.geteuid",
"steam_backlog_enforcer.game_install.os.geteuid",
return_value=1000,
),
patch("python_pkg.steam_backlog_enforcer.game_install.time.sleep"),
patch("steam_backlog_enforcer.game_install.time.sleep"),
):
_ensure_steam_running()
@ -265,15 +265,15 @@ class TestEnsureSteamRunning:
mock_result = MagicMock(returncode=1)
with (
patch(
"python_pkg.steam_backlog_enforcer.game_install.subprocess.run",
"steam_backlog_enforcer.game_install.subprocess.run",
return_value=mock_result,
),
patch(
"python_pkg.steam_backlog_enforcer.game_install.subprocess.Popen",
"steam_backlog_enforcer.game_install.subprocess.Popen",
side_effect=FileNotFoundError,
),
patch(
"python_pkg.steam_backlog_enforcer.game_install.os.geteuid",
"steam_backlog_enforcer.game_install.os.geteuid",
return_value=1000,
),
):

View File

@ -5,7 +5,7 @@ from __future__ import annotations
from typing import TYPE_CHECKING
from unittest.mock import MagicMock, patch
from python_pkg.steam_backlog_enforcer.game_install import (
from steam_backlog_enforcer.game_install import (
_remove_game_dirs,
uninstall_game,
uninstall_other_games,
@ -14,7 +14,7 @@ from python_pkg.steam_backlog_enforcer.game_install import (
if TYPE_CHECKING:
from pathlib import Path
PKG = "python_pkg.steam_backlog_enforcer.game_install"
PKG = "steam_backlog_enforcer.game_install"
class TestRemoveGameDirs:

View File

@ -5,7 +5,7 @@ from __future__ import annotations
from typing import TYPE_CHECKING
from unittest.mock import MagicMock, patch
from python_pkg.steam_backlog_enforcer.game_install import (
from steam_backlog_enforcer.game_install import (
_read_install_dir,
_remove_manifest,
get_installed_games,
@ -16,7 +16,7 @@ if TYPE_CHECKING:
from pathlib import Path
PKG = "python_pkg.steam_backlog_enforcer.game_install"
PKG = "steam_backlog_enforcer.game_install"
class TestInstallGame:
@ -26,21 +26,21 @@ class TestInstallGame:
manifest = tmp_path / "appmanifest_440.acf"
manifest.touch()
with patch(
"python_pkg.steam_backlog_enforcer.game_install.STEAMAPPS_PATH", tmp_path
"steam_backlog_enforcer.game_install.STEAMAPPS_PATH", tmp_path
):
assert install_game(440, "TF2", "steam123") is True
def test_use_steam_protocol_success(self, tmp_path: Path) -> None:
with (
patch(
"python_pkg.steam_backlog_enforcer.game_install.STEAMAPPS_PATH",
"steam_backlog_enforcer.game_install.STEAMAPPS_PATH",
tmp_path,
),
patch(
"python_pkg.steam_backlog_enforcer.game_install._ensure_steam_running"
"steam_backlog_enforcer.game_install._ensure_steam_running"
),
patch(
"python_pkg.steam_backlog_enforcer.game_install._trigger_steam_install",
"steam_backlog_enforcer.game_install._trigger_steam_install",
return_value=True,
),
):
@ -49,18 +49,18 @@ class TestInstallGame:
def test_use_steam_protocol_fallback(self, tmp_path: Path) -> None:
with (
patch(
"python_pkg.steam_backlog_enforcer.game_install.STEAMAPPS_PATH",
"steam_backlog_enforcer.game_install.STEAMAPPS_PATH",
tmp_path,
),
patch(
"python_pkg.steam_backlog_enforcer.game_install._ensure_steam_running"
"steam_backlog_enforcer.game_install._ensure_steam_running"
),
patch(
"python_pkg.steam_backlog_enforcer.game_install._trigger_steam_install",
"steam_backlog_enforcer.game_install._trigger_steam_install",
return_value=False,
),
patch(
"python_pkg.steam_backlog_enforcer.game_install.os.geteuid",
"steam_backlog_enforcer.game_install.os.geteuid",
return_value=1000,
),
):
@ -70,26 +70,26 @@ class TestInstallGame:
def test_manifest_write_as_root(self, tmp_path: Path) -> None:
with (
patch(
"python_pkg.steam_backlog_enforcer.game_install.STEAMAPPS_PATH",
"steam_backlog_enforcer.game_install.STEAMAPPS_PATH",
tmp_path,
),
patch(
"python_pkg.steam_backlog_enforcer.game_install._ensure_steam_running"
"steam_backlog_enforcer.game_install._ensure_steam_running"
),
patch(
"python_pkg.steam_backlog_enforcer.game_install.os.geteuid",
"steam_backlog_enforcer.game_install.os.geteuid",
return_value=0,
),
patch(
"python_pkg.steam_backlog_enforcer.game_install._get_real_user",
"steam_backlog_enforcer.game_install._get_real_user",
return_value="alice",
),
patch(
"python_pkg.steam_backlog_enforcer.game_install._get_uid_gid_for_user",
"steam_backlog_enforcer.game_install._get_uid_gid_for_user",
return_value=(1001, 1001),
),
patch(
"python_pkg.steam_backlog_enforcer.game_install.os.chown"
"steam_backlog_enforcer.game_install.os.chown"
) as mock_chown,
):
assert install_game(440, "TF2", "s1") is True
@ -99,14 +99,14 @@ class TestInstallGame:
# Make steamapps path not writable
with (
patch(
"python_pkg.steam_backlog_enforcer.game_install.STEAMAPPS_PATH",
"steam_backlog_enforcer.game_install.STEAMAPPS_PATH",
tmp_path / "nonexistent" / "deep",
),
patch(
"python_pkg.steam_backlog_enforcer.game_install._ensure_steam_running"
"steam_backlog_enforcer.game_install._ensure_steam_running"
),
patch(
"python_pkg.steam_backlog_enforcer.game_install.os.geteuid",
"steam_backlog_enforcer.game_install.os.geteuid",
return_value=1000,
),
):
@ -115,14 +115,14 @@ class TestInstallGame:
def test_empty_game_name(self, tmp_path: Path) -> None:
with (
patch(
"python_pkg.steam_backlog_enforcer.game_install.STEAMAPPS_PATH",
"steam_backlog_enforcer.game_install.STEAMAPPS_PATH",
tmp_path,
),
patch(
"python_pkg.steam_backlog_enforcer.game_install._ensure_steam_running"
"steam_backlog_enforcer.game_install._ensure_steam_running"
),
patch(
"python_pkg.steam_backlog_enforcer.game_install.os.geteuid",
"steam_backlog_enforcer.game_install.os.geteuid",
return_value=1000,
),
):
@ -131,18 +131,18 @@ class TestInstallGame:
def test_manifest_not_root_no_chown(self, tmp_path: Path) -> None:
with (
patch(
"python_pkg.steam_backlog_enforcer.game_install.STEAMAPPS_PATH",
"steam_backlog_enforcer.game_install.STEAMAPPS_PATH",
tmp_path,
),
patch(
"python_pkg.steam_backlog_enforcer.game_install._ensure_steam_running"
"steam_backlog_enforcer.game_install._ensure_steam_running"
),
patch(
"python_pkg.steam_backlog_enforcer.game_install.os.geteuid",
"steam_backlog_enforcer.game_install.os.geteuid",
return_value=1000,
),
patch(
"python_pkg.steam_backlog_enforcer.game_install.os.chown"
"steam_backlog_enforcer.game_install.os.chown"
) as mock_chown,
):
assert install_game(440, "TF2", "s1") is True
@ -152,22 +152,22 @@ class TestInstallGame:
"""When real user IS root, don't chown."""
with (
patch(
"python_pkg.steam_backlog_enforcer.game_install.STEAMAPPS_PATH",
"steam_backlog_enforcer.game_install.STEAMAPPS_PATH",
tmp_path,
),
patch(
"python_pkg.steam_backlog_enforcer.game_install._ensure_steam_running"
"steam_backlog_enforcer.game_install._ensure_steam_running"
),
patch(
"python_pkg.steam_backlog_enforcer.game_install.os.geteuid",
"steam_backlog_enforcer.game_install.os.geteuid",
return_value=0,
),
patch(
"python_pkg.steam_backlog_enforcer.game_install._get_real_user",
"steam_backlog_enforcer.game_install._get_real_user",
return_value="root",
),
patch(
"python_pkg.steam_backlog_enforcer.game_install.os.chown"
"steam_backlog_enforcer.game_install.os.chown"
) as mock_chown,
):
assert install_game(440, "TF2", "s1") is True
@ -181,7 +181,7 @@ class TestGetInstalledGames:
manifest = tmp_path / "appmanifest_440.acf"
manifest.write_text('"appid"\t\t"440"\n"name"\t\t"Team Fortress 2"\n')
with patch(
"python_pkg.steam_backlog_enforcer.game_install.STEAMAPPS_PATH", tmp_path
"steam_backlog_enforcer.game_install.STEAMAPPS_PATH", tmp_path
):
result = get_installed_games()
assert result == [(440, "Team Fortress 2")]
@ -190,14 +190,14 @@ class TestGetInstalledGames:
manifest = tmp_path / "appmanifest_440.acf"
manifest.write_text('"appid"\t\t"440"\n')
with patch(
"python_pkg.steam_backlog_enforcer.game_install.STEAMAPPS_PATH", tmp_path
"steam_backlog_enforcer.game_install.STEAMAPPS_PATH", tmp_path
):
result = get_installed_games()
assert result == [(440, "Unknown (440)")]
def test_empty_dir(self, tmp_path: Path) -> None:
with patch(
"python_pkg.steam_backlog_enforcer.game_install.STEAMAPPS_PATH", tmp_path
"steam_backlog_enforcer.game_install.STEAMAPPS_PATH", tmp_path
):
result = get_installed_games()
assert result == []
@ -206,7 +206,7 @@ class TestGetInstalledGames:
manifest = tmp_path / "appmanifest_440.acf"
manifest.write_text('"name"\t\t"NoAppId"\n')
with patch(
"python_pkg.steam_backlog_enforcer.game_install.STEAMAPPS_PATH", tmp_path
"steam_backlog_enforcer.game_install.STEAMAPPS_PATH", tmp_path
):
result = get_installed_games()
assert result == []
@ -219,7 +219,7 @@ class TestReadInstallDir:
manifest = tmp_path / "appmanifest_440.acf"
manifest.write_text('"installdir"\t\t"Team Fortress 2"\n')
with patch(
"python_pkg.steam_backlog_enforcer.game_install.STEAMAPPS_PATH", tmp_path
"steam_backlog_enforcer.game_install.STEAMAPPS_PATH", tmp_path
):
result = _read_install_dir(manifest)
assert result == tmp_path / "common" / "Team Fortress 2"
@ -228,7 +228,7 @@ class TestReadInstallDir:
manifest = tmp_path / "appmanifest_440.acf"
manifest.write_text('"appid"\t\t"440"\n')
with patch(
"python_pkg.steam_backlog_enforcer.game_install.STEAMAPPS_PATH", tmp_path
"steam_backlog_enforcer.game_install.STEAMAPPS_PATH", tmp_path
):
assert _read_install_dir(manifest) is None

View File

@ -9,14 +9,14 @@ from unittest.mock import AsyncMock, MagicMock, patch
import aiohttp
from python_pkg.steam_backlog_enforcer._hltb_search import (
from steam_backlog_enforcer._hltb_search import (
_AuthInfo,
_build_search_payload,
_get_hltb_search_url,
_pick_best_hltb_entry,
_similarity,
)
from python_pkg.steam_backlog_enforcer.hltb import (
from steam_backlog_enforcer.hltb import (
_get_auth_info,
load_hltb_cache,
save_hltb_cache,
@ -33,7 +33,7 @@ class TestHltbCache:
cache_file = tmp_path / "hltb_cache.json"
cache_file.write_text(json.dumps({"440": 10.5}), encoding="utf-8")
with patch(
"python_pkg.steam_backlog_enforcer._hltb_types.HLTB_CACHE_FILE", cache_file
"steam_backlog_enforcer._hltb_types.HLTB_CACHE_FILE", cache_file
):
result = load_hltb_cache()
assert result == {440: 10.5}
@ -41,7 +41,7 @@ class TestHltbCache:
def test_load_cache_missing(self, tmp_path: Path) -> None:
cache_file = tmp_path / "nonexistent.json"
with patch(
"python_pkg.steam_backlog_enforcer._hltb_types.HLTB_CACHE_FILE", cache_file
"steam_backlog_enforcer._hltb_types.HLTB_CACHE_FILE", cache_file
):
assert load_hltb_cache() == {}
@ -49,7 +49,7 @@ class TestHltbCache:
cache_file = tmp_path / "hltb_cache.json"
cache_file.write_text("not json", encoding="utf-8")
with patch(
"python_pkg.steam_backlog_enforcer._hltb_types.HLTB_CACHE_FILE", cache_file
"steam_backlog_enforcer._hltb_types.HLTB_CACHE_FILE", cache_file
):
assert load_hltb_cache() == {}
@ -57,17 +57,17 @@ class TestHltbCache:
cache_file = tmp_path / "hltb_cache.json"
with (
patch(
"python_pkg.steam_backlog_enforcer._hltb_types.HLTB_CACHE_FILE",
"steam_backlog_enforcer._hltb_types.HLTB_CACHE_FILE",
cache_file,
),
patch("python_pkg.steam_backlog_enforcer._hltb_types.CONFIG_DIR", tmp_path),
patch("steam_backlog_enforcer._hltb_types.CONFIG_DIR", tmp_path),
):
save_hltb_cache({440: 10.5})
assert cache_file.exists()
def test_save_cache_os_error(self, tmp_path: Path) -> None:
with patch(
"python_pkg.steam_backlog_enforcer._hltb_types._atomic_write",
"steam_backlog_enforcer._hltb_types._atomic_write",
side_effect=OSError("disk full"),
):
save_hltb_cache({440: 10.5}) # Should not raise
@ -80,7 +80,7 @@ class TestGetHltbSearchUrl:
mock_info = MagicMock()
mock_info.search_url = "/api/search/abc"
with patch(
"python_pkg.steam_backlog_enforcer._hltb_search.HTMLRequests"
"steam_backlog_enforcer._hltb_search.HTMLRequests"
) as mock_html:
mock_html.send_website_request_getcode.return_value = mock_info
mock_html.BASE_URL = "https://howlongtobeat.com"
@ -89,7 +89,7 @@ class TestGetHltbSearchUrl:
def test_fallback_url(self) -> None:
with patch(
"python_pkg.steam_backlog_enforcer._hltb_search.HTMLRequests"
"steam_backlog_enforcer._hltb_search.HTMLRequests"
) as mock_html:
mock_html.send_website_request_getcode.return_value = None
url = _get_hltb_search_url()
@ -99,7 +99,7 @@ class TestGetHltbSearchUrl:
mock_info = MagicMock()
mock_info.search_url = "/api/search/xyz"
with patch(
"python_pkg.steam_backlog_enforcer._hltb_search.HTMLRequests"
"steam_backlog_enforcer._hltb_search.HTMLRequests"
) as mock_html:
mock_html.send_website_request_getcode.side_effect = [None, mock_info]
mock_html.BASE_URL = "https://howlongtobeat.com"
@ -108,7 +108,7 @@ class TestGetHltbSearchUrl:
def test_exception_fallback(self) -> None:
with patch(
"python_pkg.steam_backlog_enforcer._hltb_search.HTMLRequests"
"steam_backlog_enforcer._hltb_search.HTMLRequests"
) as mock_html:
mock_html.send_website_request_getcode.side_effect = RuntimeError
url = _get_hltb_search_url()

View File

@ -10,7 +10,7 @@ from unittest.mock import AsyncMock, MagicMock, patch
import aiohttp
from typing_extensions import Self
from python_pkg.steam_backlog_enforcer._hltb_detail import (
from steam_backlog_enforcer._hltb_detail import (
_apply_dlc_leisure_overrides,
_as_positive_int,
_collect_dlc_relationships,
@ -22,7 +22,7 @@ from python_pkg.steam_backlog_enforcer._hltb_detail import (
_fetch_leisure_times,
_process_game_detail,
)
from python_pkg.steam_backlog_enforcer._hltb_types import (
from steam_backlog_enforcer._hltb_types import (
_SAVE_INTERVAL,
HLTBResult,
_HLTBExtras,
@ -155,7 +155,7 @@ class TestInternalHelpers:
async def _run() -> dict[int, float]:
async with aiohttp.ClientSession() as session:
with patch(
"python_pkg.steam_backlog_enforcer._hltb_detail._fetch_detail_one",
"steam_backlog_enforcer._hltb_detail._fetch_detail_one",
new_callable=AsyncMock,
return_value=None,
):
@ -176,7 +176,7 @@ class TestInternalHelpers:
async def _run() -> dict[int, float]:
async with aiohttp.ClientSession() as session:
with patch(
"python_pkg.steam_backlog_enforcer._hltb_detail._fetch_detail_one",
"steam_backlog_enforcer._hltb_detail._fetch_detail_one",
new_callable=AsyncMock,
return_value=bad_dlc_data,
):
@ -339,7 +339,7 @@ class TestFetchLeisureTimes:
}
cache: dict[int, float] = {}
with patch(
"python_pkg.steam_backlog_enforcer._hltb_detail._fetch_detail_one",
"steam_backlog_enforcer._hltb_detail._fetch_detail_one",
new_callable=AsyncMock,
return_value=game_data,
):
@ -378,7 +378,7 @@ class TestFetchLeisureTimes:
]
cache: dict[int, float] = {}
with patch(
"python_pkg.steam_backlog_enforcer._hltb_detail._fetch_detail_one",
"steam_backlog_enforcer._hltb_detail._fetch_detail_one",
new_callable=AsyncMock,
return_value=None,
):
@ -399,7 +399,7 @@ class TestFetchLeisureTimes:
game_data: dict[str, Any] = {"game": [], "relationships": []}
cache: dict[int, float] = {}
with patch(
"python_pkg.steam_backlog_enforcer._hltb_detail._fetch_detail_one",
"steam_backlog_enforcer._hltb_detail._fetch_detail_one",
new_callable=AsyncMock,
return_value=game_data,
):
@ -424,7 +424,7 @@ class TestFetchLeisureTimes:
cache: dict[int, float] = {}
cb = MagicMock()
with patch(
"python_pkg.steam_backlog_enforcer._hltb_detail._fetch_detail_one",
"steam_backlog_enforcer._hltb_detail._fetch_detail_one",
new_callable=AsyncMock,
return_value=game_data,
):
@ -450,12 +450,12 @@ class TestFetchLeisureTimes:
cache: dict[int, float] = {}
with (
patch(
"python_pkg.steam_backlog_enforcer._hltb_detail._fetch_detail_one",
"steam_backlog_enforcer._hltb_detail._fetch_detail_one",
new_callable=AsyncMock,
return_value=game_data,
),
patch(
"python_pkg.steam_backlog_enforcer._hltb_detail.save_hltb_cache"
"steam_backlog_enforcer._hltb_detail.save_hltb_cache"
) as mock_save,
):
asyncio.run(_fetch_leisure_times(results, cache, {}, None))
@ -481,7 +481,7 @@ class TestFetchLeisureTimes:
}
cache: dict[int, float] = {}
with patch(
"python_pkg.steam_backlog_enforcer._hltb_detail._fetch_detail_one",
"steam_backlog_enforcer._hltb_detail._fetch_detail_one",
new_callable=AsyncMock,
side_effect=[base_data, dlc_data],
):
@ -507,7 +507,7 @@ class TestFetchLeisureTimes:
}
cache: dict[int, float] = {}
with patch(
"python_pkg.steam_backlog_enforcer._hltb_detail._fetch_detail_one",
"steam_backlog_enforcer._hltb_detail._fetch_detail_one",
new_callable=AsyncMock,
side_effect=[base_data, None],
):
@ -535,7 +535,7 @@ class TestFetchLeisureTimes:
cache: dict[int, float] = {}
extras = _HLTBExtras(count_comp={440: 5})
with patch(
"python_pkg.steam_backlog_enforcer._hltb_detail._fetch_detail_one",
"steam_backlog_enforcer._hltb_detail._fetch_detail_one",
new_callable=AsyncMock,
return_value=game_data,
):
@ -561,7 +561,7 @@ class TestFetchLeisureTimes:
cache: dict[int, float] = {}
extras = _HLTBExtras(count_comp={440: 5})
with patch(
"python_pkg.steam_backlog_enforcer._hltb_detail._fetch_detail_one",
"steam_backlog_enforcer._hltb_detail._fetch_detail_one",
new_callable=AsyncMock,
return_value=game_data,
):

View File

@ -8,8 +8,8 @@ from unittest.mock import MagicMock, patch
from typing_extensions import Self
from python_pkg.steam_backlog_enforcer._hltb_search import _AuthInfo
from python_pkg.steam_backlog_enforcer.hltb import (
from steam_backlog_enforcer._hltb_search import _AuthInfo
from steam_backlog_enforcer.hltb import (
HLTB_BASE_URL,
HLTBResult,
_fetch_batch_confidence_only,
@ -21,9 +21,9 @@ from python_pkg.steam_backlog_enforcer.hltb import (
)
if TYPE_CHECKING:
from python_pkg.steam_backlog_enforcer._hltb_types import _HLTBExtras
from steam_backlog_enforcer._hltb_types import _HLTBExtras
PKG = "python_pkg.steam_backlog_enforcer.hltb"
PKG = "steam_backlog_enforcer.hltb"
class TestFetchHltbTimesCached:

View File

@ -4,7 +4,7 @@ from __future__ import annotations
from unittest.mock import patch
from python_pkg.steam_backlog_enforcer.hltb import (
from steam_backlog_enforcer.hltb import (
HLTBResult,
fetch_hltb_times,
)
@ -21,7 +21,7 @@ class TestFetchHltbTimes:
app_id=440, game_name="TF2", completionist_hours=50.0, similarity=1.0
)
with patch(
"python_pkg.steam_backlog_enforcer.hltb._fetch_batch",
"steam_backlog_enforcer.hltb._fetch_batch",
return_value=[mock_result],
):
results = fetch_hltb_times([(440, "TF2")])
@ -29,7 +29,7 @@ class TestFetchHltbTimes:
def test_none_cache(self) -> None:
with patch(
"python_pkg.steam_backlog_enforcer.hltb._fetch_batch",
"steam_backlog_enforcer.hltb._fetch_batch",
return_value=[],
):
results = fetch_hltb_times([(440, "TF2")])
@ -37,7 +37,7 @@ class TestFetchHltbTimes:
def test_explicit_cache(self) -> None:
with patch(
"python_pkg.steam_backlog_enforcer.hltb._fetch_batch",
"steam_backlog_enforcer.hltb._fetch_batch",
return_value=[],
):
cache: dict[int, float] = {440: 10.0}

View File

@ -9,12 +9,12 @@ from unittest.mock import AsyncMock, MagicMock, patch
import aiohttp
from typing_extensions import Self
from python_pkg.steam_backlog_enforcer._hltb_search import (
from steam_backlog_enforcer._hltb_search import (
_fetch_batch,
_search_one,
_SearchCtx,
)
from python_pkg.steam_backlog_enforcer._hltb_types import (
from steam_backlog_enforcer._hltb_types import (
_SAVE_INTERVAL,
)
@ -260,7 +260,7 @@ class TestSearchOne:
ctx.counter["done"] = _SAVE_INTERVAL - 1
with patch(
"python_pkg.steam_backlog_enforcer._hltb_search.save_hltb_cache"
"steam_backlog_enforcer._hltb_search.save_hltb_cache"
) as mock_save:
asyncio.run(_search_one(asyncio.Semaphore(1), ctx, 440, "TF2"))
mock_save.assert_called_once()
@ -272,11 +272,11 @@ class TestFetchBatchHltb:
def test_no_auth(self) -> None:
with (
patch(
"python_pkg.steam_backlog_enforcer._hltb_search._get_hltb_search_url",
"steam_backlog_enforcer._hltb_search._get_hltb_search_url",
return_value="https://example.com",
),
patch(
"python_pkg.steam_backlog_enforcer._hltb_search._get_auth_info",
"steam_backlog_enforcer._hltb_search._get_auth_info",
new_callable=AsyncMock,
return_value=None,
),

View File

@ -9,16 +9,16 @@ from unittest.mock import AsyncMock, MagicMock, patch
from typing_extensions import Self
from python_pkg.steam_backlog_enforcer._hltb_detail import (
from steam_backlog_enforcer._hltb_detail import (
_extract_leisure_hours,
_parse_game_page,
)
from python_pkg.steam_backlog_enforcer._hltb_search import (
from steam_backlog_enforcer._hltb_search import (
_build_search_variants,
_fetch_batch,
_pick_best_hltb_entry,
)
from python_pkg.steam_backlog_enforcer._hltb_types import (
from steam_backlog_enforcer._hltb_types import (
HLTBResult,
_AuthInfo,
)
@ -110,16 +110,16 @@ class TestPickBestEntry:
auth = _AuthInfo("token123", "ign_x", "ff")
with (
patch(
"python_pkg.steam_backlog_enforcer._hltb_search._get_hltb_search_url",
"steam_backlog_enforcer._hltb_search._get_hltb_search_url",
return_value="https://example.com",
),
patch(
"python_pkg.steam_backlog_enforcer._hltb_search._get_auth_info",
"steam_backlog_enforcer._hltb_search._get_auth_info",
new_callable=AsyncMock,
return_value=auth,
),
patch(
"python_pkg.steam_backlog_enforcer._hltb_search._search_one",
"steam_backlog_enforcer._hltb_search._search_one",
new_callable=AsyncMock,
return_value=HLTBResult(
app_id=440,
@ -130,7 +130,7 @@ class TestPickBestEntry:
),
),
patch(
"python_pkg.steam_backlog_enforcer._hltb_search._fetch_leisure_times",
"steam_backlog_enforcer._hltb_search._fetch_leisure_times",
new_callable=AsyncMock,
),
):
@ -141,21 +141,21 @@ class TestPickBestEntry:
auth = _AuthInfo("tok123")
with (
patch(
"python_pkg.steam_backlog_enforcer._hltb_search._get_hltb_search_url",
"steam_backlog_enforcer._hltb_search._get_hltb_search_url",
return_value="https://example.com",
),
patch(
"python_pkg.steam_backlog_enforcer._hltb_search._get_auth_info",
"steam_backlog_enforcer._hltb_search._get_auth_info",
new_callable=AsyncMock,
return_value=auth,
),
patch(
"python_pkg.steam_backlog_enforcer._hltb_search._search_one",
"steam_backlog_enforcer._hltb_search._search_one",
new_callable=AsyncMock,
return_value=None,
),
patch(
"python_pkg.steam_backlog_enforcer._hltb_search._fetch_leisure_times",
"steam_backlog_enforcer._hltb_search._fetch_leisure_times",
new_callable=AsyncMock,
),
):
@ -166,21 +166,21 @@ class TestPickBestEntry:
auth = _AuthInfo("tok123")
with (
patch(
"python_pkg.steam_backlog_enforcer._hltb_search._get_hltb_search_url",
"steam_backlog_enforcer._hltb_search._get_hltb_search_url",
return_value="https://example.com",
),
patch(
"python_pkg.steam_backlog_enforcer._hltb_search._get_auth_info",
"steam_backlog_enforcer._hltb_search._get_auth_info",
new_callable=AsyncMock,
return_value=auth,
),
patch(
"python_pkg.steam_backlog_enforcer._hltb_search._search_one",
"steam_backlog_enforcer._hltb_search._search_one",
new_callable=AsyncMock,
return_value=None,
),
patch(
"python_pkg.steam_backlog_enforcer._hltb_search._fetch_leisure_times",
"steam_backlog_enforcer._hltb_search._fetch_leisure_times",
new_callable=AsyncMock,
),
):

View File

@ -8,7 +8,7 @@ from unittest.mock import AsyncMock, MagicMock, patch
import pytest
from python_pkg.steam_backlog_enforcer.library_hider import (
from steam_backlog_enforcer.library_hider import (
_cdp_result_value,
_evaluate_js,
_evaluate_js_async,
@ -37,7 +37,7 @@ class TestGetSharedJsWsUrl:
mock_resp = MagicMock()
mock_resp.json.return_value = targets
with patch(
"python_pkg.steam_backlog_enforcer.library_hider.requests.get",
"steam_backlog_enforcer.library_hider.requests.get",
return_value=mock_resp,
):
result = _get_shared_js_ws_url()
@ -48,14 +48,14 @@ class TestGetSharedJsWsUrl:
mock_resp = MagicMock()
mock_resp.json.return_value = targets
with patch(
"python_pkg.steam_backlog_enforcer.library_hider.requests.get",
"steam_backlog_enforcer.library_hider.requests.get",
return_value=mock_resp,
):
assert _get_shared_js_ws_url() is None
def test_connection_error(self) -> None:
with patch(
"python_pkg.steam_backlog_enforcer.library_hider.requests.get",
"steam_backlog_enforcer.library_hider.requests.get",
side_effect=OSError,
):
assert _get_shared_js_ws_url() is None
@ -74,7 +74,7 @@ class TestEvaluateJsAsync:
mock_ws.__aexit__ = AsyncMock(return_value=False)
with patch(
"python_pkg.steam_backlog_enforcer.library_hider.websockets.connect",
"steam_backlog_enforcer.library_hider.websockets.connect",
return_value=mock_ws,
):
result = asyncio.run(_evaluate_js_async("ws://test", "1+1"))
@ -87,11 +87,11 @@ class TestEvaluateJs:
def test_success(self) -> None:
with (
patch(
"python_pkg.steam_backlog_enforcer.library_hider._get_shared_js_ws_url",
"steam_backlog_enforcer.library_hider._get_shared_js_ws_url",
return_value="ws://test",
),
patch(
"python_pkg.steam_backlog_enforcer.library_hider.asyncio.run",
"steam_backlog_enforcer.library_hider.asyncio.run",
return_value={"result": {"result": {"value": "ok"}}},
),
):
@ -101,7 +101,7 @@ class TestEvaluateJs:
def test_no_ws_url(self) -> None:
with (
patch(
"python_pkg.steam_backlog_enforcer.library_hider._get_shared_js_ws_url",
"steam_backlog_enforcer.library_hider._get_shared_js_ws_url",
return_value=None,
),
pytest.raises(RuntimeError, match="SharedJSContext not found"),
@ -136,7 +136,7 @@ class TestIsSteamRunning:
def test_running(self) -> None:
mock_result = MagicMock(returncode=0)
with patch(
"python_pkg.steam_backlog_enforcer.library_hider.subprocess.run",
"steam_backlog_enforcer.library_hider.subprocess.run",
return_value=mock_result,
):
assert _is_steam_running() is True
@ -144,7 +144,7 @@ class TestIsSteamRunning:
def test_not_running(self) -> None:
mock_result = MagicMock(returncode=1)
with patch(
"python_pkg.steam_backlog_enforcer.library_hider.subprocess.run",
"steam_backlog_enforcer.library_hider.subprocess.run",
return_value=mock_result,
):
assert _is_steam_running() is False
@ -155,14 +155,14 @@ class TestSteamHasDebugPort:
def test_has_port(self) -> None:
with patch(
"python_pkg.steam_backlog_enforcer.library_hider._get_shared_js_ws_url",
"steam_backlog_enforcer.library_hider._get_shared_js_ws_url",
return_value="ws://test",
):
assert _steam_has_debug_port() is True
def test_no_port(self) -> None:
with patch(
"python_pkg.steam_backlog_enforcer.library_hider._get_shared_js_ws_url",
"steam_backlog_enforcer.library_hider._get_shared_js_ws_url",
return_value=None,
):
assert _steam_has_debug_port() is False
@ -173,7 +173,7 @@ class TestWaitForCdpReady:
def test_ready_immediately(self) -> None:
with patch(
"python_pkg.steam_backlog_enforcer.library_hider._get_shared_js_ws_url",
"steam_backlog_enforcer.library_hider._get_shared_js_ws_url",
return_value="ws://test",
):
assert _wait_for_cdp_ready() is True
@ -181,14 +181,14 @@ class TestWaitForCdpReady:
def test_timeout(self) -> None:
with (
patch(
"python_pkg.steam_backlog_enforcer.library_hider._get_shared_js_ws_url",
"steam_backlog_enforcer.library_hider._get_shared_js_ws_url",
return_value=None,
),
patch(
"python_pkg.steam_backlog_enforcer.library_hider.time.sleep",
"steam_backlog_enforcer.library_hider.time.sleep",
),
patch(
"python_pkg.steam_backlog_enforcer.library_hider._STEAM_STARTUP_WAIT",
"steam_backlog_enforcer.library_hider._STEAM_STARTUP_WAIT",
2,
),
):
@ -201,11 +201,11 @@ class TestWaitForCollectionsReady:
def test_ready(self) -> None:
with (
patch(
"python_pkg.steam_backlog_enforcer.library_hider._evaluate_js",
"steam_backlog_enforcer.library_hider._evaluate_js",
return_value={"result": {"result": {"value": "ok"}}},
),
patch(
"python_pkg.steam_backlog_enforcer.library_hider._cdp_result_value",
"steam_backlog_enforcer.library_hider._cdp_result_value",
return_value="ok",
),
):
@ -214,18 +214,18 @@ class TestWaitForCollectionsReady:
def test_not_ready_then_ready(self) -> None:
with (
patch(
"python_pkg.steam_backlog_enforcer.library_hider._evaluate_js",
"steam_backlog_enforcer.library_hider._evaluate_js",
return_value={"result": {"result": {"value": "not_ready"}}},
),
patch(
"python_pkg.steam_backlog_enforcer.library_hider._cdp_result_value",
"steam_backlog_enforcer.library_hider._cdp_result_value",
side_effect=["not_ready", "ok"],
),
patch(
"python_pkg.steam_backlog_enforcer.library_hider.time.sleep",
"steam_backlog_enforcer.library_hider.time.sleep",
),
patch(
"python_pkg.steam_backlog_enforcer.library_hider._STEAM_STARTUP_WAIT",
"steam_backlog_enforcer.library_hider._STEAM_STARTUP_WAIT",
2,
),
):
@ -234,14 +234,14 @@ class TestWaitForCollectionsReady:
def test_timeout(self) -> None:
with (
patch(
"python_pkg.steam_backlog_enforcer.library_hider._evaluate_js",
"steam_backlog_enforcer.library_hider._evaluate_js",
side_effect=RuntimeError,
),
patch(
"python_pkg.steam_backlog_enforcer.library_hider.time.sleep",
"steam_backlog_enforcer.library_hider.time.sleep",
),
patch(
"python_pkg.steam_backlog_enforcer.library_hider._STEAM_STARTUP_WAIT",
"steam_backlog_enforcer.library_hider._STEAM_STARTUP_WAIT",
2,
),
):
@ -255,10 +255,10 @@ class TestShutdownSteam:
mock_result = MagicMock(returncode=1) # Not running
with (
patch(
"python_pkg.steam_backlog_enforcer.library_hider._run_as_user",
"steam_backlog_enforcer.library_hider._run_as_user",
),
patch(
"python_pkg.steam_backlog_enforcer.library_hider.subprocess.run",
"steam_backlog_enforcer.library_hider.subprocess.run",
return_value=mock_result,
),
):
@ -268,21 +268,21 @@ class TestShutdownSteam:
results = [MagicMock(returncode=0), MagicMock(returncode=1)]
with (
patch(
"python_pkg.steam_backlog_enforcer.library_hider._run_as_user",
"steam_backlog_enforcer.library_hider._run_as_user",
),
patch(
"python_pkg.steam_backlog_enforcer.library_hider.subprocess.run",
"steam_backlog_enforcer.library_hider.subprocess.run",
side_effect=results,
),
patch(
"python_pkg.steam_backlog_enforcer.library_hider.time.sleep",
"steam_backlog_enforcer.library_hider.time.sleep",
),
):
_shutdown_steam()
def test_file_not_found(self) -> None:
with patch(
"python_pkg.steam_backlog_enforcer.library_hider._run_as_user",
"steam_backlog_enforcer.library_hider._run_as_user",
side_effect=FileNotFoundError,
):
_shutdown_steam() # Should not raise
@ -291,14 +291,14 @@ class TestShutdownSteam:
mock_result = MagicMock(returncode=0) # Still running
with (
patch(
"python_pkg.steam_backlog_enforcer.library_hider._run_as_user",
"steam_backlog_enforcer.library_hider._run_as_user",
),
patch(
"python_pkg.steam_backlog_enforcer.library_hider.subprocess.run",
"steam_backlog_enforcer.library_hider.subprocess.run",
return_value=mock_result,
),
patch(
"python_pkg.steam_backlog_enforcer.library_hider.time.sleep",
"steam_backlog_enforcer.library_hider.time.sleep",
),
):
_shutdown_steam() # Should complete loop without raising
@ -309,7 +309,7 @@ class TestLaunchSteamWithDebug:
def test_launches(self) -> None:
with patch(
"python_pkg.steam_backlog_enforcer.library_hider._run_as_user",
"steam_backlog_enforcer.library_hider._run_as_user",
) as mock_run:
_launch_steam_with_debug()
mock_run.assert_called_once()
@ -320,7 +320,7 @@ class TestEnsureSteamDebugPort:
def test_already_available(self) -> None:
with patch(
"python_pkg.steam_backlog_enforcer.library_hider._steam_has_debug_port",
"steam_backlog_enforcer.library_hider._steam_has_debug_port",
return_value=True,
):
ensure_steam_debug_port()
@ -328,22 +328,22 @@ class TestEnsureSteamDebugPort:
def test_starts_fresh(self) -> None:
with (
patch(
"python_pkg.steam_backlog_enforcer.library_hider._steam_has_debug_port",
"steam_backlog_enforcer.library_hider._steam_has_debug_port",
return_value=False,
),
patch(
"python_pkg.steam_backlog_enforcer.library_hider._is_steam_running",
"steam_backlog_enforcer.library_hider._is_steam_running",
return_value=False,
),
patch(
"python_pkg.steam_backlog_enforcer.library_hider._launch_steam_with_debug",
"steam_backlog_enforcer.library_hider._launch_steam_with_debug",
),
patch(
"python_pkg.steam_backlog_enforcer.library_hider._wait_for_cdp_ready",
"steam_backlog_enforcer.library_hider._wait_for_cdp_ready",
return_value=True,
),
patch(
"python_pkg.steam_backlog_enforcer.library_hider._wait_for_collections_ready",
"steam_backlog_enforcer.library_hider._wait_for_collections_ready",
return_value=True,
),
):
@ -352,25 +352,25 @@ class TestEnsureSteamDebugPort:
def test_restarts_running_steam(self) -> None:
with (
patch(
"python_pkg.steam_backlog_enforcer.library_hider._steam_has_debug_port",
"steam_backlog_enforcer.library_hider._steam_has_debug_port",
return_value=False,
),
patch(
"python_pkg.steam_backlog_enforcer.library_hider._is_steam_running",
"steam_backlog_enforcer.library_hider._is_steam_running",
return_value=True,
),
patch(
"python_pkg.steam_backlog_enforcer.library_hider._shutdown_steam",
"steam_backlog_enforcer.library_hider._shutdown_steam",
),
patch(
"python_pkg.steam_backlog_enforcer.library_hider._launch_steam_with_debug",
"steam_backlog_enforcer.library_hider._launch_steam_with_debug",
),
patch(
"python_pkg.steam_backlog_enforcer.library_hider._wait_for_cdp_ready",
"steam_backlog_enforcer.library_hider._wait_for_cdp_ready",
return_value=True,
),
patch(
"python_pkg.steam_backlog_enforcer.library_hider._wait_for_collections_ready",
"steam_backlog_enforcer.library_hider._wait_for_collections_ready",
return_value=True,
),
):
@ -379,18 +379,18 @@ class TestEnsureSteamDebugPort:
def test_cdp_timeout(self) -> None:
with (
patch(
"python_pkg.steam_backlog_enforcer.library_hider._steam_has_debug_port",
"steam_backlog_enforcer.library_hider._steam_has_debug_port",
return_value=False,
),
patch(
"python_pkg.steam_backlog_enforcer.library_hider._is_steam_running",
"steam_backlog_enforcer.library_hider._is_steam_running",
return_value=False,
),
patch(
"python_pkg.steam_backlog_enforcer.library_hider._launch_steam_with_debug",
"steam_backlog_enforcer.library_hider._launch_steam_with_debug",
),
patch(
"python_pkg.steam_backlog_enforcer.library_hider._wait_for_cdp_ready",
"steam_backlog_enforcer.library_hider._wait_for_cdp_ready",
return_value=False,
),
pytest.raises(RuntimeError, match="Timed out waiting for Steam CDP"),
@ -400,22 +400,22 @@ class TestEnsureSteamDebugPort:
def test_collections_timeout(self) -> None:
with (
patch(
"python_pkg.steam_backlog_enforcer.library_hider._steam_has_debug_port",
"steam_backlog_enforcer.library_hider._steam_has_debug_port",
return_value=False,
),
patch(
"python_pkg.steam_backlog_enforcer.library_hider._is_steam_running",
"steam_backlog_enforcer.library_hider._is_steam_running",
return_value=False,
),
patch(
"python_pkg.steam_backlog_enforcer.library_hider._launch_steam_with_debug",
"steam_backlog_enforcer.library_hider._launch_steam_with_debug",
),
patch(
"python_pkg.steam_backlog_enforcer.library_hider._wait_for_cdp_ready",
"steam_backlog_enforcer.library_hider._wait_for_cdp_ready",
return_value=True,
),
patch(
"python_pkg.steam_backlog_enforcer.library_hider._wait_for_collections_ready",
"steam_backlog_enforcer.library_hider._wait_for_collections_ready",
return_value=False,
),
pytest.raises(

View File

@ -6,14 +6,14 @@ import os
import tempfile
from unittest.mock import MagicMock, patch
from python_pkg.steam_backlog_enforcer.library_hider import (
from steam_backlog_enforcer.library_hider import (
_run_as_user,
hide_other_games,
restart_steam,
unhide_all_games,
)
PKG = "python_pkg.steam_backlog_enforcer.library_hider"
PKG = "steam_backlog_enforcer.library_hider"
class TestRunAsUser:

View File

@ -9,9 +9,9 @@ from unittest.mock import patch
import pytest
from python_pkg.steam_backlog_enforcer._whitelist import WHITELIST_COOLDOWN_SECONDS
from python_pkg.steam_backlog_enforcer.config import Config, State
from python_pkg.steam_backlog_enforcer.main import (
from steam_backlog_enforcer._whitelist import WHITELIST_COOLDOWN_SECONDS
from steam_backlog_enforcer.config import Config, State
from steam_backlog_enforcer.main import (
cmd_add_exception,
cmd_buy_dlc,
cmd_hide,
@ -27,7 +27,7 @@ from python_pkg.steam_backlog_enforcer.main import (
main,
)
PKG = "python_pkg.steam_backlog_enforcer.main"
PKG = "steam_backlog_enforcer.main"
def _snap(

View File

@ -5,16 +5,16 @@ from __future__ import annotations
from typing import Any
from unittest.mock import MagicMock, patch
from python_pkg.steam_backlog_enforcer._cmd_done import (
from steam_backlog_enforcer._cmd_done import (
_enforce_on_done,
_finalize_completion,
cmd_done,
)
from python_pkg.steam_backlog_enforcer.config import Config, State
from python_pkg.steam_backlog_enforcer.steam_api import GameInfo
from steam_backlog_enforcer.config import Config, State
from steam_backlog_enforcer.steam_api import GameInfo
CMD_DONE_PKG = "python_pkg.steam_backlog_enforcer._cmd_done"
PKG = "python_pkg.steam_backlog_enforcer.main"
CMD_DONE_PKG = "steam_backlog_enforcer._cmd_done"
PKG = "steam_backlog_enforcer.main"
def _snap(

View File

@ -8,15 +8,15 @@ from unittest.mock import MagicMock, patch
import pytest
from python_pkg.steam_backlog_enforcer._cmd_done import (
from steam_backlog_enforcer._cmd_done import (
cmd_done,
)
from python_pkg.steam_backlog_enforcer.config import Config, State
from python_pkg.steam_backlog_enforcer.main import cmd_pick, main
from python_pkg.steam_backlog_enforcer.steam_api import GameInfo
from steam_backlog_enforcer.config import Config, State
from steam_backlog_enforcer.main import cmd_pick, main
from steam_backlog_enforcer.steam_api import GameInfo
CMD_DONE_PKG = "python_pkg.steam_backlog_enforcer._cmd_done"
PKG = "python_pkg.steam_backlog_enforcer.main"
CMD_DONE_PKG = "steam_backlog_enforcer._cmd_done"
PKG = "steam_backlog_enforcer.main"
def _snap(

View File

@ -6,8 +6,8 @@ import json
from typing import TYPE_CHECKING
from unittest.mock import patch
from python_pkg.steam_backlog_enforcer import _cmd_done
from python_pkg.steam_backlog_enforcer._hltb_types import (
from steam_backlog_enforcer import _cmd_done
from steam_backlog_enforcer._hltb_types import (
HLTBResult,
_HLTBExtras,
load_hltb_cache,
@ -16,15 +16,15 @@ from python_pkg.steam_backlog_enforcer._hltb_types import (
load_hltb_polls_cache,
save_hltb_cache,
)
from python_pkg.steam_backlog_enforcer.config import State
from python_pkg.steam_backlog_enforcer.steam_api import GameInfo
from steam_backlog_enforcer.config import State
from steam_backlog_enforcer.steam_api import GameInfo
if TYPE_CHECKING:
from pathlib import Path
_TYPES = "python_pkg.steam_backlog_enforcer._hltb_types"
_CMD = "python_pkg.steam_backlog_enforcer._cmd_done"
_SCAN = "python_pkg.steam_backlog_enforcer.scanning"
_TYPES = "steam_backlog_enforcer._hltb_types"
_CMD = "steam_backlog_enforcer._cmd_done"
_SCAN = "steam_backlog_enforcer.scanning"
class TestCacheSchema:

View File

@ -6,17 +6,17 @@ import json
from typing import TYPE_CHECKING
from unittest.mock import MagicMock, patch
from python_pkg.steam_backlog_enforcer import _cmd_done, _scanning_confidence, scanning
from python_pkg.steam_backlog_enforcer.config import State
from python_pkg.steam_backlog_enforcer.steam_api import GameInfo
from steam_backlog_enforcer import _cmd_done, _scanning_confidence, scanning
from steam_backlog_enforcer.config import State
from steam_backlog_enforcer.steam_api import GameInfo
if TYPE_CHECKING:
from pathlib import Path
_TYPES = "python_pkg.steam_backlog_enforcer._hltb_types"
_CMD = "python_pkg.steam_backlog_enforcer._cmd_done"
_SCAN = "python_pkg.steam_backlog_enforcer.scanning"
_SCANCONF = "python_pkg.steam_backlog_enforcer._scanning_confidence"
_TYPES = "steam_backlog_enforcer._hltb_types"
_CMD = "steam_backlog_enforcer._cmd_done"
_SCAN = "steam_backlog_enforcer.scanning"
_SCANCONF = "steam_backlog_enforcer._scanning_confidence"
def _state(finished: list[int], current: int | None = None) -> State:
@ -231,8 +231,8 @@ class TestScanningPollsIntegration:
def test_do_scan_kept_assignment_missing_game(self) -> None:
"""Covers scanning.py 110->116: current_app_id set but game absent."""
from python_pkg.steam_backlog_enforcer.config import Config
from python_pkg.steam_backlog_enforcer.scanning import do_scan
from steam_backlog_enforcer.config import Config
from steam_backlog_enforcer.scanning import do_scan
other = GameInfo(
app_id=999,

View File

@ -9,7 +9,7 @@ from unittest.mock import AsyncMock, MagicMock, patch
import aiohttp
from python_pkg.steam_backlog_enforcer.protondb import (
from steam_backlog_enforcer.protondb import (
HTTP_NOT_FOUND,
ProtonDBRating,
_fetch_batch,
@ -116,7 +116,7 @@ class TestProtonDBCache:
cache_file = tmp_path / "protondb_cache.json"
cache_file.write_text(json.dumps({"440": {"tier": "gold"}}), encoding="utf-8")
with patch(
"python_pkg.steam_backlog_enforcer.protondb.PROTONDB_CACHE_FILE",
"steam_backlog_enforcer.protondb.PROTONDB_CACHE_FILE",
cache_file,
):
result = _load_cache()
@ -125,7 +125,7 @@ class TestProtonDBCache:
def test_load_cache_missing(self, tmp_path: Path) -> None:
cache_file = tmp_path / "nonexistent.json"
with patch(
"python_pkg.steam_backlog_enforcer.protondb.PROTONDB_CACHE_FILE",
"steam_backlog_enforcer.protondb.PROTONDB_CACHE_FILE",
cache_file,
):
assert _load_cache() == {}
@ -135,10 +135,10 @@ class TestProtonDBCache:
config_dir = tmp_path
with (
patch(
"python_pkg.steam_backlog_enforcer.protondb.PROTONDB_CACHE_FILE",
"steam_backlog_enforcer.protondb.PROTONDB_CACHE_FILE",
cache_file,
),
patch("python_pkg.steam_backlog_enforcer.protondb.CONFIG_DIR", config_dir),
patch("steam_backlog_enforcer.protondb.CONFIG_DIR", config_dir),
):
_save_cache({"440": {"tier": "gold"}})
assert cache_file.exists()
@ -231,7 +231,7 @@ class TestFetchBatch:
def test_returns_ratings(self) -> None:
rating = ProtonDBRating(app_id=440, tier="gold")
with patch(
"python_pkg.steam_backlog_enforcer.protondb._fetch_one",
"steam_backlog_enforcer.protondb._fetch_one",
new_callable=AsyncMock,
return_value=rating,
):
@ -251,7 +251,7 @@ class TestFetchBatch:
return rating if app_id == 440 else None
with patch(
"python_pkg.steam_backlog_enforcer.protondb._fetch_one",
"steam_backlog_enforcer.protondb._fetch_one",
side_effect=mock_fetch_one,
):
result = asyncio.run(_fetch_batch([440, 999]))
@ -266,7 +266,7 @@ class TestFetchProtondbRatings:
cache_file = tmp_path / "protondb_cache.json"
cache_file.write_text(json.dumps({"440": {"tier": "gold"}}), encoding="utf-8")
with patch(
"python_pkg.steam_backlog_enforcer.protondb.PROTONDB_CACHE_FILE",
"steam_backlog_enforcer.protondb.PROTONDB_CACHE_FILE",
cache_file,
):
result = fetch_protondb_ratings([440])
@ -278,12 +278,12 @@ class TestFetchProtondbRatings:
config_dir = tmp_path
with (
patch(
"python_pkg.steam_backlog_enforcer.protondb.PROTONDB_CACHE_FILE",
"steam_backlog_enforcer.protondb.PROTONDB_CACHE_FILE",
cache_file,
),
patch("python_pkg.steam_backlog_enforcer.protondb.CONFIG_DIR", config_dir),
patch("steam_backlog_enforcer.protondb.CONFIG_DIR", config_dir),
patch(
"python_pkg.steam_backlog_enforcer.protondb._fetch_batch",
"steam_backlog_enforcer.protondb._fetch_batch",
return_value=[ProtonDBRating(app_id=440, tier="platinum")],
),
):

View File

@ -5,14 +5,14 @@ from __future__ import annotations
from typing import TYPE_CHECKING
from unittest.mock import MagicMock, patch
from python_pkg.steam_backlog_enforcer.config import Config, State
from python_pkg.steam_backlog_enforcer.protondb import ProtonDBRating
from python_pkg.steam_backlog_enforcer.scanning import (
from steam_backlog_enforcer.config import Config, State
from steam_backlog_enforcer.protondb import ProtonDBRating
from steam_backlog_enforcer.scanning import (
_pick_playable_candidate,
do_scan,
pick_next_game,
)
from python_pkg.steam_backlog_enforcer.steam_api import GameInfo
from steam_backlog_enforcer.steam_api import GameInfo
if TYPE_CHECKING:
from collections.abc import Callable
@ -56,24 +56,24 @@ class TestDoScan:
mock_client.build_game_list.side_effect = build_game_list
with (
patch(
"python_pkg.steam_backlog_enforcer.scanning.SteamAPIClient",
"steam_backlog_enforcer.scanning.SteamAPIClient",
return_value=mock_client,
),
patch(
"python_pkg.steam_backlog_enforcer.scanning.fetch_hltb_times_cached",
"steam_backlog_enforcer.scanning.fetch_hltb_times_cached",
side_effect=lambda _games, progress_cb=None: (
progress_cb(1, 1, 1, "TF2") if progress_cb else None,
{440: 20.0},
)[1],
),
patch(
"python_pkg.steam_backlog_enforcer.scanning.save_snapshot",
"steam_backlog_enforcer.scanning.save_snapshot",
),
patch(
"python_pkg.steam_backlog_enforcer.scanning.pick_next_game",
"steam_backlog_enforcer.scanning.pick_next_game",
) as mock_pick,
patch(
"python_pkg.steam_backlog_enforcer.scanning._echo",
"steam_backlog_enforcer.scanning._echo",
),
):
config = Config(steam_api_key="k", steam_id="i")
@ -97,16 +97,16 @@ class TestDoScan:
mock_client.build_game_list.side_effect = build_game_list
with (
patch(
"python_pkg.steam_backlog_enforcer.scanning.SteamAPIClient",
"steam_backlog_enforcer.scanning.SteamAPIClient",
return_value=mock_client,
),
patch(
"python_pkg.steam_backlog_enforcer.scanning.save_snapshot",
"steam_backlog_enforcer.scanning.save_snapshot",
),
patch(
"python_pkg.steam_backlog_enforcer.scanning.pick_next_game",
"steam_backlog_enforcer.scanning.pick_next_game",
) as mock_pick,
patch("python_pkg.steam_backlog_enforcer.scanning._echo"),
patch("steam_backlog_enforcer.scanning._echo"),
):
config = Config(steam_api_key="k", steam_id="i")
state = State()
@ -120,20 +120,20 @@ class TestDoScan:
mock_client.build_game_list.return_value = [game]
with (
patch(
"python_pkg.steam_backlog_enforcer.scanning.SteamAPIClient",
"steam_backlog_enforcer.scanning.SteamAPIClient",
return_value=mock_client,
),
patch(
"python_pkg.steam_backlog_enforcer.scanning.fetch_hltb_times_cached",
"steam_backlog_enforcer.scanning.fetch_hltb_times_cached",
return_value={440: 20.0},
),
patch(
"python_pkg.steam_backlog_enforcer.scanning.save_snapshot",
"steam_backlog_enforcer.scanning.save_snapshot",
),
patch(
"python_pkg.steam_backlog_enforcer.scanning.pick_next_game",
"steam_backlog_enforcer.scanning.pick_next_game",
) as mock_pick,
patch("python_pkg.steam_backlog_enforcer.scanning._echo"),
patch("steam_backlog_enforcer.scanning._echo"),
):
config = Config(steam_api_key="k", steam_id="i")
state = State(current_app_id=440)
@ -149,12 +149,12 @@ class TestPickPlayableCandidate:
game = _game(app_id=440, name="TF2")
with (
patch(
"python_pkg.steam_backlog_enforcer.scanning.fetch_protondb_ratings",
"steam_backlog_enforcer.scanning.fetch_protondb_ratings",
return_value={
440: ProtonDBRating(app_id=440, tier="gold"),
},
),
patch("python_pkg.steam_backlog_enforcer.scanning._echo"),
patch("steam_backlog_enforcer.scanning._echo"),
):
result = _pick_playable_candidate([game])
assert result is not None
@ -165,13 +165,13 @@ class TestPickPlayableCandidate:
good = _game(app_id=2, name="Good")
with (
patch(
"python_pkg.steam_backlog_enforcer.scanning.fetch_protondb_ratings",
"steam_backlog_enforcer.scanning.fetch_protondb_ratings",
return_value={
1: ProtonDBRating(app_id=1, tier="borked"),
2: ProtonDBRating(app_id=2, tier="platinum"),
},
),
patch("python_pkg.steam_backlog_enforcer.scanning._echo"),
patch("steam_backlog_enforcer.scanning._echo"),
):
result = _pick_playable_candidate([bad, good])
assert result is not None
@ -181,12 +181,12 @@ class TestPickPlayableCandidate:
game = _game(app_id=1, name="Bad")
with (
patch(
"python_pkg.steam_backlog_enforcer.scanning.fetch_protondb_ratings",
"steam_backlog_enforcer.scanning.fetch_protondb_ratings",
return_value={
1: ProtonDBRating(app_id=1, tier="borked"),
},
),
patch("python_pkg.steam_backlog_enforcer.scanning._echo"),
patch("steam_backlog_enforcer.scanning._echo"),
):
assert _pick_playable_candidate([game]) is None
@ -198,12 +198,12 @@ class TestPickPlayableCandidate:
game = _game(app_id=440, name="TF2")
with (
patch(
"python_pkg.steam_backlog_enforcer.scanning.fetch_protondb_ratings",
"steam_backlog_enforcer.scanning.fetch_protondb_ratings",
return_value={
440: ProtonDBRating(app_id=440, tier="platinum"),
},
),
patch("python_pkg.steam_backlog_enforcer.scanning._echo"),
patch("steam_backlog_enforcer.scanning._echo"),
):
result = _pick_playable_candidate([game])
assert result is not None
@ -219,17 +219,17 @@ class TestPickNextGame:
state = State()
with (
patch(
"python_pkg.steam_backlog_enforcer.scanning._pick_playable_candidate",
"steam_backlog_enforcer.scanning._pick_playable_candidate",
side_effect=lambda c: c[0] if c else None,
),
patch("python_pkg.steam_backlog_enforcer.scanning._echo"),
patch("python_pkg.steam_backlog_enforcer._scanning_confidence._echo"),
patch("steam_backlog_enforcer.scanning._echo"),
patch("steam_backlog_enforcer._scanning_confidence._echo"),
patch(
"python_pkg.steam_backlog_enforcer.scanning.is_game_installed",
"steam_backlog_enforcer.scanning.is_game_installed",
return_value=True,
),
patch(
"python_pkg.steam_backlog_enforcer.scanning.uninstall_other_games",
"steam_backlog_enforcer.scanning.uninstall_other_games",
return_value=0,
),
patch("builtins.input", return_value="1"),
@ -241,7 +241,7 @@ class TestPickNextGame:
g1 = _game(app_id=1, total=5, unlocked=5)
config = Config(steam_api_key="k", steam_id="i")
state = State()
with patch("python_pkg.steam_backlog_enforcer.scanning._echo"):
with patch("steam_backlog_enforcer.scanning._echo"):
pick_next_game([g1], state, config)
assert state.current_app_id is None
@ -252,16 +252,16 @@ class TestPickNextGame:
state = State(finished_app_ids=[1])
with (
patch(
"python_pkg.steam_backlog_enforcer.scanning._pick_playable_candidate",
"steam_backlog_enforcer.scanning._pick_playable_candidate",
side_effect=lambda c: c[0] if c else None,
),
patch("python_pkg.steam_backlog_enforcer.scanning._echo"),
patch("steam_backlog_enforcer.scanning._echo"),
patch(
"python_pkg.steam_backlog_enforcer.scanning.is_game_installed",
"steam_backlog_enforcer.scanning.is_game_installed",
return_value=True,
),
patch(
"python_pkg.steam_backlog_enforcer.scanning.uninstall_other_games",
"steam_backlog_enforcer.scanning.uninstall_other_games",
return_value=0,
),
patch("builtins.input", return_value="1"),
@ -275,10 +275,10 @@ class TestPickNextGame:
state = State()
with (
patch(
"python_pkg.steam_backlog_enforcer.scanning._pick_playable_candidate",
"steam_backlog_enforcer.scanning._pick_playable_candidate",
return_value=None,
),
patch("python_pkg.steam_backlog_enforcer.scanning._echo"),
patch("steam_backlog_enforcer.scanning._echo"),
):
pick_next_game([g1], state, config)
assert state.current_app_id is None
@ -289,17 +289,17 @@ class TestPickNextGame:
state = State()
with (
patch(
"python_pkg.steam_backlog_enforcer.scanning._pick_playable_candidate",
"steam_backlog_enforcer.scanning._pick_playable_candidate",
side_effect=lambda c: c[0] if c else None,
),
patch("python_pkg.steam_backlog_enforcer.scanning._echo"),
patch("python_pkg.steam_backlog_enforcer._scanning_confidence._echo"),
patch("steam_backlog_enforcer.scanning._echo"),
patch("steam_backlog_enforcer._scanning_confidence._echo"),
patch(
"python_pkg.steam_backlog_enforcer.scanning.uninstall_other_games",
"steam_backlog_enforcer.scanning.uninstall_other_games",
return_value=2,
),
patch(
"python_pkg.steam_backlog_enforcer.scanning.is_game_installed",
"steam_backlog_enforcer.scanning.is_game_installed",
return_value=True,
),
patch("builtins.input", return_value="1"),
@ -313,17 +313,17 @@ class TestPickNextGame:
state = State()
with (
patch(
"python_pkg.steam_backlog_enforcer.scanning._pick_playable_candidate",
"steam_backlog_enforcer.scanning._pick_playable_candidate",
side_effect=lambda c: c[0] if c else None,
),
patch("python_pkg.steam_backlog_enforcer.scanning._echo"),
patch("python_pkg.steam_backlog_enforcer._scanning_confidence._echo"),
patch("steam_backlog_enforcer.scanning._echo"),
patch("steam_backlog_enforcer._scanning_confidence._echo"),
patch(
"python_pkg.steam_backlog_enforcer.scanning.is_game_installed",
"steam_backlog_enforcer.scanning.is_game_installed",
return_value=False,
),
patch(
"python_pkg.steam_backlog_enforcer.scanning.install_game"
"steam_backlog_enforcer.scanning.install_game"
) as mock_install,
patch("builtins.input", return_value="1"),
):
@ -337,16 +337,16 @@ class TestPickNextGame:
state = State()
with (
patch(
"python_pkg.steam_backlog_enforcer.scanning._pick_playable_candidate",
"steam_backlog_enforcer.scanning._pick_playable_candidate",
side_effect=lambda c: c[0] if c else None,
),
patch("python_pkg.steam_backlog_enforcer.scanning._echo"),
patch("steam_backlog_enforcer.scanning._echo"),
patch(
"python_pkg.steam_backlog_enforcer.scanning.is_game_installed",
"steam_backlog_enforcer.scanning.is_game_installed",
return_value=True,
),
patch(
"python_pkg.steam_backlog_enforcer.scanning.uninstall_other_games",
"steam_backlog_enforcer.scanning.uninstall_other_games",
return_value=0,
),
patch("builtins.input", return_value="1"),
@ -361,16 +361,16 @@ class TestPickNextGame:
state = State()
with (
patch(
"python_pkg.steam_backlog_enforcer.scanning._pick_playable_candidate",
"steam_backlog_enforcer.scanning._pick_playable_candidate",
side_effect=lambda c: c[0] if c else None,
),
patch("python_pkg.steam_backlog_enforcer.scanning._echo"),
patch("steam_backlog_enforcer.scanning._echo"),
patch(
"python_pkg.steam_backlog_enforcer.scanning.is_game_installed",
"steam_backlog_enforcer.scanning.is_game_installed",
return_value=True,
),
patch(
"python_pkg.steam_backlog_enforcer.scanning.uninstall_other_games",
"steam_backlog_enforcer.scanning.uninstall_other_games",
return_value=0,
),
patch("builtins.input", return_value="1"),
@ -390,23 +390,23 @@ class TestPickNextGame:
state = State()
with (
patch(
"python_pkg.steam_backlog_enforcer.scanning._pick_playable_candidate",
"steam_backlog_enforcer.scanning._pick_playable_candidate",
side_effect=lambda c: c[0] if c else None,
),
patch(
"python_pkg.steam_backlog_enforcer.scanning._echo",
"steam_backlog_enforcer.scanning._echo",
side_effect=lambda *a, **_: echoed.append(a[0]),
),
patch(
"python_pkg.steam_backlog_enforcer._scanning_confidence._echo",
"steam_backlog_enforcer._scanning_confidence._echo",
side_effect=lambda *a, **_: echoed.append(a[0]),
),
patch(
"python_pkg.steam_backlog_enforcer.scanning.is_game_installed",
"steam_backlog_enforcer.scanning.is_game_installed",
return_value=True,
),
patch(
"python_pkg.steam_backlog_enforcer.scanning.uninstall_other_games",
"steam_backlog_enforcer.scanning.uninstall_other_games",
return_value=0,
),
patch("builtins.input", return_value="1"),
@ -428,15 +428,15 @@ class TestPickNextGame:
state = State()
with (
patch(
"python_pkg.steam_backlog_enforcer.scanning._echo",
"steam_backlog_enforcer.scanning._echo",
side_effect=lambda *a, **_: echoed.append(a[0]),
),
patch(
"python_pkg.steam_backlog_enforcer._scanning_confidence._echo",
"steam_backlog_enforcer._scanning_confidence._echo",
side_effect=lambda *a, **_: echoed.append(a[0]),
),
patch(
"python_pkg.steam_backlog_enforcer.scanning._pick_playable_candidate",
"steam_backlog_enforcer.scanning._pick_playable_candidate",
return_value=None,
) as mock_pick,
):

View File

@ -5,13 +5,13 @@ from __future__ import annotations
from typing import Any
from unittest.mock import MagicMock, patch
from python_pkg.steam_backlog_enforcer.config import Config, State
from python_pkg.steam_backlog_enforcer.scanning import (
from steam_backlog_enforcer.config import Config, State
from steam_backlog_enforcer.scanning import (
_check_game_tampering,
detect_tampering,
)
PKG = "python_pkg.steam_backlog_enforcer.scanning"
PKG = "steam_backlog_enforcer.scanning"
def _entry(

View File

@ -5,9 +5,9 @@ from __future__ import annotations
import contextlib
from unittest.mock import patch
from python_pkg.steam_backlog_enforcer.config import Config, State
from python_pkg.steam_backlog_enforcer.scanning import pick_next_game
from python_pkg.steam_backlog_enforcer.steam_api import GameInfo
from steam_backlog_enforcer.config import Config, State
from steam_backlog_enforcer.scanning import pick_next_game
from steam_backlog_enforcer.steam_api import GameInfo
def _game(
@ -50,27 +50,27 @@ class TestPickNextGame:
with (
patch(
"python_pkg.steam_backlog_enforcer._scanning_confidence._refresh_candidate_confidence",
"steam_backlog_enforcer._scanning_confidence._refresh_candidate_confidence",
side_effect=refresh_side_effect,
) as mock_refresh,
patch(
"python_pkg.steam_backlog_enforcer.scanning._pick_playable_candidate",
"steam_backlog_enforcer.scanning._pick_playable_candidate",
side_effect=lambda c: c[0] if c else None,
),
patch(
"python_pkg.steam_backlog_enforcer.scanning._echo",
"steam_backlog_enforcer.scanning._echo",
side_effect=lambda *a, **_: echoed.append(a[0]),
),
patch(
"python_pkg.steam_backlog_enforcer._scanning_confidence._echo",
"steam_backlog_enforcer._scanning_confidence._echo",
side_effect=lambda *a, **_: echoed.append(a[0]),
),
patch(
"python_pkg.steam_backlog_enforcer.scanning.is_game_installed",
"steam_backlog_enforcer.scanning.is_game_installed",
return_value=True,
),
patch(
"python_pkg.steam_backlog_enforcer.scanning.uninstall_other_games",
"steam_backlog_enforcer.scanning.uninstall_other_games",
return_value=0,
),
patch("builtins.input", return_value="1"),
@ -93,19 +93,19 @@ class TestPickNextGame:
with (
patch(
"python_pkg.steam_backlog_enforcer._scanning_confidence._refresh_candidate_confidence_batch"
"steam_backlog_enforcer._scanning_confidence._refresh_candidate_confidence_batch"
) as mock_refresh_batch,
patch(
"python_pkg.steam_backlog_enforcer.scanning._pick_playable_candidate",
"steam_backlog_enforcer.scanning._pick_playable_candidate",
side_effect=lambda c: c[0] if c else None,
),
patch("python_pkg.steam_backlog_enforcer.scanning._echo"),
patch("steam_backlog_enforcer.scanning._echo"),
patch(
"python_pkg.steam_backlog_enforcer.scanning.is_game_installed",
"steam_backlog_enforcer.scanning.is_game_installed",
return_value=True,
),
patch(
"python_pkg.steam_backlog_enforcer.scanning.uninstall_other_games",
"steam_backlog_enforcer.scanning.uninstall_other_games",
return_value=0,
),
patch("builtins.input", return_value="1"),
@ -131,27 +131,27 @@ class TestPickNextGame:
with (
patch(
"python_pkg.steam_backlog_enforcer._scanning_confidence.load_hltb_polls_cache",
"steam_backlog_enforcer._scanning_confidence.load_hltb_polls_cache",
return_value={1: 1, 2: 3},
),
patch(
"python_pkg.steam_backlog_enforcer._scanning_confidence.load_hltb_count_comp_cache",
"steam_backlog_enforcer._scanning_confidence.load_hltb_count_comp_cache",
return_value={1: 8, 2: 20},
),
patch(
"python_pkg.steam_backlog_enforcer._scanning_confidence._refresh_candidate_confidence_batch"
"steam_backlog_enforcer._scanning_confidence._refresh_candidate_confidence_batch"
) as mock_refresh_batch,
patch(
"python_pkg.steam_backlog_enforcer.scanning._pick_playable_candidate",
"steam_backlog_enforcer.scanning._pick_playable_candidate",
side_effect=lambda c: c[0] if c else None,
),
patch("python_pkg.steam_backlog_enforcer.scanning._echo"),
patch("steam_backlog_enforcer.scanning._echo"),
patch(
"python_pkg.steam_backlog_enforcer.scanning.is_game_installed",
"steam_backlog_enforcer.scanning.is_game_installed",
return_value=True,
),
patch(
"python_pkg.steam_backlog_enforcer.scanning.uninstall_other_games",
"steam_backlog_enforcer.scanning.uninstall_other_games",
return_value=0,
),
patch("builtins.input", return_value="1"),
@ -178,16 +178,16 @@ class TestPickNextGame:
with (
patch(
"python_pkg.steam_backlog_enforcer.scanning._pick_playable_candidate",
"steam_backlog_enforcer.scanning._pick_playable_candidate",
side_effect=playable_side_effect,
),
patch("python_pkg.steam_backlog_enforcer.scanning._echo"),
patch("steam_backlog_enforcer.scanning._echo"),
patch(
"python_pkg.steam_backlog_enforcer.scanning.is_game_installed",
"steam_backlog_enforcer.scanning.is_game_installed",
return_value=True,
),
patch(
"python_pkg.steam_backlog_enforcer.scanning.uninstall_other_games",
"steam_backlog_enforcer.scanning.uninstall_other_games",
return_value=0,
),
patch("builtins.input", return_value="1"),
@ -205,16 +205,16 @@ class TestPickNextGame:
state = State()
with (
patch(
"python_pkg.steam_backlog_enforcer.scanning._pick_playable_candidate",
"steam_backlog_enforcer.scanning._pick_playable_candidate",
side_effect=lambda c: c[0] if c else None,
),
patch("python_pkg.steam_backlog_enforcer.scanning._echo"),
patch("steam_backlog_enforcer.scanning._echo"),
patch(
"python_pkg.steam_backlog_enforcer.scanning.is_game_installed",
"steam_backlog_enforcer.scanning.is_game_installed",
return_value=True,
),
patch(
"python_pkg.steam_backlog_enforcer.scanning.uninstall_other_games",
"steam_backlog_enforcer.scanning.uninstall_other_games",
return_value=0,
),
patch("builtins.input", return_value="2"),
@ -230,19 +230,19 @@ class TestPickNextGame:
echoed: list[str] = []
with (
patch(
"python_pkg.steam_backlog_enforcer.scanning._pick_playable_candidate",
"steam_backlog_enforcer.scanning._pick_playable_candidate",
side_effect=lambda c: c[0] if c else None,
),
patch(
"python_pkg.steam_backlog_enforcer.scanning._echo",
"steam_backlog_enforcer.scanning._echo",
side_effect=lambda *a, **_: echoed.append(a[0]),
),
patch(
"python_pkg.steam_backlog_enforcer.scanning.is_game_installed",
"steam_backlog_enforcer.scanning.is_game_installed",
return_value=True,
),
patch(
"python_pkg.steam_backlog_enforcer.scanning.uninstall_other_games",
"steam_backlog_enforcer.scanning.uninstall_other_games",
return_value=0,
),
patch("builtins.input", side_effect=["abc", "1"]),
@ -259,19 +259,19 @@ class TestPickNextGame:
echoed: list[str] = []
with (
patch(
"python_pkg.steam_backlog_enforcer.scanning._pick_playable_candidate",
"steam_backlog_enforcer.scanning._pick_playable_candidate",
side_effect=lambda c: c[0] if c else None,
),
patch(
"python_pkg.steam_backlog_enforcer.scanning._echo",
"steam_backlog_enforcer.scanning._echo",
side_effect=lambda *a, **_: echoed.append(a[0]),
),
patch(
"python_pkg.steam_backlog_enforcer.scanning.is_game_installed",
"steam_backlog_enforcer.scanning.is_game_installed",
return_value=True,
),
patch(
"python_pkg.steam_backlog_enforcer.scanning.uninstall_other_games",
"steam_backlog_enforcer.scanning.uninstall_other_games",
return_value=0,
),
patch("builtins.input", side_effect=["99", "1"]),
@ -289,30 +289,30 @@ class TestPickNextGameSequential:
stack = contextlib.ExitStack()
stack.enter_context(
patch(
"python_pkg.steam_backlog_enforcer.scanning._pick_playable_candidate",
"steam_backlog_enforcer.scanning._pick_playable_candidate",
side_effect=lambda c: c[0] if c else None,
)
)
stack.enter_context(
patch(
"python_pkg.steam_backlog_enforcer.scanning._echo",
"steam_backlog_enforcer.scanning._echo",
side_effect=lambda *a, **_: echoed.append(a[0]),
)
)
stack.enter_context(
patch(
"python_pkg.steam_backlog_enforcer.scanning.is_game_installed",
"steam_backlog_enforcer.scanning.is_game_installed",
return_value=True,
)
)
stack.enter_context(
patch(
"python_pkg.steam_backlog_enforcer.scanning.uninstall_other_games",
"steam_backlog_enforcer.scanning.uninstall_other_games",
return_value=0,
)
)
stack.enter_context(
patch("python_pkg.steam_backlog_enforcer.config._atomic_write")
patch("steam_backlog_enforcer.config._atomic_write")
)
return stack
@ -363,14 +363,14 @@ class TestPickNextGameSequential:
echoed: list[str] = []
with (
patch(
"python_pkg.steam_backlog_enforcer.scanning._pick_playable_candidate",
"steam_backlog_enforcer.scanning._pick_playable_candidate",
return_value=None,
),
patch(
"python_pkg.steam_backlog_enforcer.scanning._echo",
"steam_backlog_enforcer.scanning._echo",
side_effect=lambda *a, **_: echoed.append(a[0]),
),
patch("python_pkg.steam_backlog_enforcer.config._atomic_write"),
patch("steam_backlog_enforcer.config._atomic_write"),
):
pick_next_game([g1], state, config, on_select=lambda _g: True)
assert state.current_app_id is None
@ -386,17 +386,17 @@ class TestPickNextGameSequential:
echoed: list[str] = []
with (
patch(
"python_pkg.steam_backlog_enforcer.scanning._pick_playable_candidate",
"steam_backlog_enforcer.scanning._pick_playable_candidate",
side_effect=lambda c: c[0] if c else None,
),
patch(
"python_pkg.steam_backlog_enforcer.scanning._echo",
"steam_backlog_enforcer.scanning._echo",
side_effect=lambda *a, **_: echoed.append(a[0]),
),
patch(
"python_pkg.steam_backlog_enforcer._scanning_confidence._refresh_candidate_confidence",
"steam_backlog_enforcer._scanning_confidence._refresh_candidate_confidence",
),
patch("python_pkg.steam_backlog_enforcer.config._atomic_write"),
patch("steam_backlog_enforcer.config._atomic_write"),
):
pick_next_game([g1], state, config, on_select=lambda _g: True)
assert state.current_app_id is None

View File

@ -4,18 +4,18 @@ from __future__ import annotations
from unittest.mock import MagicMock, patch
from python_pkg.steam_backlog_enforcer._scanning_confidence import (
from steam_backlog_enforcer._scanning_confidence import (
_filter_hltb_confident_candidates,
_force_refresh_candidate_confidence,
_refresh_candidate_confidence_batch,
)
from python_pkg.steam_backlog_enforcer.config import Config, State
from python_pkg.steam_backlog_enforcer.scanning import (
from steam_backlog_enforcer.config import Config, State
from steam_backlog_enforcer.scanning import (
_collect_top_candidates,
_pick_next_shortest_candidate,
do_check,
)
from python_pkg.steam_backlog_enforcer.steam_api import GameInfo
from steam_backlog_enforcer.steam_api import GameInfo
def _game(
@ -44,7 +44,7 @@ class TestCollectTopCandidates:
"""Returns at most n qualified candidates."""
games = [_game(app_id=i, name=f"G{i}", hours=float(i)) for i in range(1, 6)]
with patch(
"python_pkg.steam_backlog_enforcer.scanning._pick_playable_candidate",
"steam_backlog_enforcer.scanning._pick_playable_candidate",
side_effect=lambda c: c[0] if c else None,
):
qualified, conf_skip, linux_skip = _collect_top_candidates(games, n=3)
@ -59,10 +59,10 @@ class TestCollectTopCandidates:
g2 = _game(app_id=2, name="Good", hours=2.0)
with (
patch(
"python_pkg.steam_backlog_enforcer.scanning._pick_playable_candidate",
"steam_backlog_enforcer.scanning._pick_playable_candidate",
side_effect=lambda c: None if c[0].app_id == 1 else c[0],
),
patch("python_pkg.steam_backlog_enforcer.scanning._echo"),
patch("steam_backlog_enforcer.scanning._echo"),
):
qualified, conf_skip, linux_skip = _collect_top_candidates([g1, g2], n=10)
assert [g.app_id for g in qualified] == [2]
@ -80,10 +80,10 @@ class TestCollectTopCandidates:
g = _game(app_id=1, name="Good", hours=1.0)
with (
patch(
"python_pkg.steam_backlog_enforcer.scanning._pick_playable_candidate",
"steam_backlog_enforcer.scanning._pick_playable_candidate",
side_effect=lambda c: c[0] if c else None,
),
patch("python_pkg.steam_backlog_enforcer.scanning._echo") as mock_echo,
patch("steam_backlog_enforcer.scanning._echo") as mock_echo,
):
_collect_top_candidates([g], n=10)
mock_echo.assert_not_called()
@ -93,7 +93,7 @@ class TestDoCheck:
"""Tests for do_check."""
def test_no_assignment(self) -> None:
with patch("python_pkg.steam_backlog_enforcer.scanning._echo") as mock_echo:
with patch("steam_backlog_enforcer.scanning._echo") as mock_echo:
do_check(Config(), State())
mock_echo.assert_called()
@ -102,11 +102,11 @@ class TestDoCheck:
mock_client.refresh_single_game.return_value = None
with (
patch(
"python_pkg.steam_backlog_enforcer.scanning.SteamAPIClient",
"steam_backlog_enforcer.scanning.SteamAPIClient",
return_value=mock_client,
),
patch("python_pkg.steam_backlog_enforcer.scanning._echo"),
patch("python_pkg.steam_backlog_enforcer.scanning.detect_tampering"),
patch("steam_backlog_enforcer.scanning._echo"),
patch("steam_backlog_enforcer.scanning.detect_tampering"),
):
state = State(current_app_id=440, current_game_name="TF2")
do_check(Config(steam_api_key="k", steam_id="i"), state)
@ -118,7 +118,7 @@ class TestConfidenceHelpers:
def test_force_refresh_candidate_confidence_delegates(self) -> None:
game = _game(app_id=10, name="A")
with patch(
"python_pkg.steam_backlog_enforcer._scanning_confidence._refresh_candidate_confidence_batch",
"steam_backlog_enforcer._scanning_confidence._refresh_candidate_confidence_batch",
) as mock_batch:
_force_refresh_candidate_confidence(game)
mock_batch.assert_called_once_with([game], force=True)
@ -128,7 +128,7 @@ class TestConfidenceHelpers:
game.comp_100_count = 3
game.count_comp = 15
with patch(
"python_pkg.steam_backlog_enforcer._scanning_confidence.fetch_hltb_confidence_cached",
"steam_backlog_enforcer._scanning_confidence.fetch_hltb_confidence_cached",
) as mock_fetch:
_refresh_candidate_confidence_batch([game], force=False)
mock_fetch.assert_not_called()
@ -139,23 +139,23 @@ class TestConfidenceHelpers:
game.count_comp = 0
with (
patch(
"python_pkg.steam_backlog_enforcer._scanning_confidence.load_hltb_cache",
"steam_backlog_enforcer._scanning_confidence.load_hltb_cache",
side_effect=[{30: 9.5}, {30: -1.0}],
),
patch(
"python_pkg.steam_backlog_enforcer._scanning_confidence.load_hltb_polls_cache",
"steam_backlog_enforcer._scanning_confidence.load_hltb_polls_cache",
return_value={30: 0},
),
patch(
"python_pkg.steam_backlog_enforcer._scanning_confidence.load_hltb_count_comp_cache",
"steam_backlog_enforcer._scanning_confidence.load_hltb_count_comp_cache",
return_value={30: 0},
),
patch(
"python_pkg.steam_backlog_enforcer._scanning_confidence.fetch_hltb_confidence_cached",
"steam_backlog_enforcer._scanning_confidence.fetch_hltb_confidence_cached",
return_value={30: -1.0},
),
patch(
"python_pkg.steam_backlog_enforcer._scanning_confidence.save_hltb_cache",
"steam_backlog_enforcer._scanning_confidence.save_hltb_cache",
) as mock_save,
):
_refresh_candidate_confidence_batch([game], force=True)
@ -170,10 +170,10 @@ class TestConfidenceHelpers:
low.count_comp = 2
with (
patch(
"python_pkg.steam_backlog_enforcer._scanning_confidence._refresh_candidate_confidence_batch",
"steam_backlog_enforcer._scanning_confidence._refresh_candidate_confidence_batch",
),
patch(
"python_pkg.steam_backlog_enforcer._scanning_confidence._echo"
"steam_backlog_enforcer._scanning_confidence._echo"
) as mock_echo,
):
result = _filter_hltb_confident_candidates([low])
@ -190,10 +190,10 @@ class TestConfidenceHelpers:
with (
patch(
"python_pkg.steam_backlog_enforcer.scanning._pick_playable_candidate",
"steam_backlog_enforcer.scanning._pick_playable_candidate",
side_effect=[None, good],
),
patch("python_pkg.steam_backlog_enforcer.scanning._echo") as mock_echo,
patch("steam_backlog_enforcer.scanning._echo") as mock_echo,
):
picked, skipped_low_conf, skipped_linux = _pick_next_shortest_candidate(
[bad, good],
@ -214,10 +214,10 @@ class TestConfidenceHelpers:
good = _game(app_id=51, name="Good", hours=2.0)
with (
patch(
"python_pkg.steam_backlog_enforcer.scanning._pick_playable_candidate",
"steam_backlog_enforcer.scanning._pick_playable_candidate",
return_value=good,
),
patch("python_pkg.steam_backlog_enforcer.scanning._echo") as mock_echo,
patch("steam_backlog_enforcer.scanning._echo") as mock_echo,
):
picked, _skipped_low_conf, skipped_linux = _pick_next_shortest_candidate(
[good],
@ -233,9 +233,9 @@ class TestConfidenceHelpers:
low_conf.count_comp = 0
with (
patch(
"python_pkg.steam_backlog_enforcer._scanning_confidence._refresh_candidate_confidence"
"steam_backlog_enforcer._scanning_confidence._refresh_candidate_confidence"
),
patch("python_pkg.steam_backlog_enforcer.scanning._echo"),
patch("steam_backlog_enforcer.scanning._echo"),
):
picked, skipped_low_conf, skipped_linux = _pick_next_shortest_candidate(
[low_conf],
@ -249,10 +249,10 @@ class TestConfidenceHelpers:
g1 = _game(app_id=10, name="Borked", hours=1.0)
with (
patch(
"python_pkg.steam_backlog_enforcer.scanning._pick_playable_candidate",
"steam_backlog_enforcer.scanning._pick_playable_candidate",
return_value=None,
),
patch("python_pkg.steam_backlog_enforcer.scanning._echo") as mock_echo,
patch("steam_backlog_enforcer.scanning._echo") as mock_echo,
):
picked, _skipped_low_conf, skipped_linux = _pick_next_shortest_candidate(
[g1],
@ -270,21 +270,21 @@ class TestConfidenceHelpers:
snap = [game.to_snapshot()]
with (
patch(
"python_pkg.steam_backlog_enforcer.scanning.SteamAPIClient",
"steam_backlog_enforcer.scanning.SteamAPIClient",
return_value=mock_client,
),
patch("python_pkg.steam_backlog_enforcer.scanning._echo"),
patch("steam_backlog_enforcer.scanning._echo"),
patch(
"python_pkg.steam_backlog_enforcer.scanning.send_notification",
"steam_backlog_enforcer.scanning.send_notification",
),
patch(
"python_pkg.steam_backlog_enforcer.scanning.load_snapshot",
"steam_backlog_enforcer.scanning.load_snapshot",
return_value=snap,
),
patch(
"python_pkg.steam_backlog_enforcer.scanning.pick_next_game",
"steam_backlog_enforcer.scanning.pick_next_game",
),
patch("python_pkg.steam_backlog_enforcer.scanning.detect_tampering"),
patch("steam_backlog_enforcer.scanning.detect_tampering"),
):
state = State(current_app_id=440, current_game_name="TF2")
do_check(Config(steam_api_key="k", steam_id="i"), state)
@ -296,18 +296,18 @@ class TestConfidenceHelpers:
mock_client.refresh_single_game.return_value = game
with (
patch(
"python_pkg.steam_backlog_enforcer.scanning.SteamAPIClient",
"steam_backlog_enforcer.scanning.SteamAPIClient",
return_value=mock_client,
),
patch("python_pkg.steam_backlog_enforcer.scanning._echo"),
patch("steam_backlog_enforcer.scanning._echo"),
patch(
"python_pkg.steam_backlog_enforcer.scanning.send_notification",
"steam_backlog_enforcer.scanning.send_notification",
),
patch(
"python_pkg.steam_backlog_enforcer.scanning.load_snapshot",
"steam_backlog_enforcer.scanning.load_snapshot",
return_value=None,
),
patch("python_pkg.steam_backlog_enforcer.scanning.detect_tampering"),
patch("steam_backlog_enforcer.scanning.detect_tampering"),
):
state = State(current_app_id=440, current_game_name="TF2")
do_check(Config(steam_api_key="k", steam_id="i"), state)
@ -318,11 +318,11 @@ class TestConfidenceHelpers:
mock_client.refresh_single_game.return_value = game
with (
patch(
"python_pkg.steam_backlog_enforcer.scanning.SteamAPIClient",
"steam_backlog_enforcer.scanning.SteamAPIClient",
return_value=mock_client,
),
patch("python_pkg.steam_backlog_enforcer.scanning._echo"),
patch("python_pkg.steam_backlog_enforcer.scanning.detect_tampering"),
patch("steam_backlog_enforcer.scanning._echo"),
patch("steam_backlog_enforcer.scanning.detect_tampering"),
):
state = State(current_app_id=440, current_game_name="TF2")
do_check(Config(steam_api_key="k", steam_id="i"), state)

View File

@ -5,7 +5,7 @@ from __future__ import annotations
from datetime import datetime, timedelta, timezone
from unittest.mock import patch
from python_pkg.steam_backlog_enforcer._stats import (
from steam_backlog_enforcer._stats import (
_ensure_rush_data,
_filter_qualifying_games,
_format_completion_date,
@ -16,11 +16,11 @@ from python_pkg.steam_backlog_enforcer._stats import (
_sum_hours,
cmd_stats,
)
from python_pkg.steam_backlog_enforcer.config import Config, State
from python_pkg.steam_backlog_enforcer.protondb import ProtonDBRating
from python_pkg.steam_backlog_enforcer.steam_api import GameInfo
from steam_backlog_enforcer.config import Config, State
from steam_backlog_enforcer.protondb import ProtonDBRating
from steam_backlog_enforcer.steam_api import GameInfo
_PKG = "python_pkg.steam_backlog_enforcer._stats"
_PKG = "steam_backlog_enforcer._stats"
def _game(

View File

@ -8,7 +8,7 @@ from unittest.mock import MagicMock, patch
import pytest
import requests
from python_pkg.steam_backlog_enforcer.steam_api import (
from steam_backlog_enforcer.steam_api import (
AchievementInfo,
GameInfo,
SteamAPIClient,
@ -163,7 +163,7 @@ class TestSteamAPIClient:
# Fill up the rate limit window
client._request_times = [__import__("time").time()] * client._max_rps
with patch(
"python_pkg.steam_backlog_enforcer.steam_api.time.sleep",
"steam_backlog_enforcer.steam_api.time.sleep",
) as mock_sleep:
# Next call should trigger sleep then succeed
client._rate_limit()

View File

@ -5,7 +5,7 @@ from __future__ import annotations
from typing import TYPE_CHECKING
from unittest.mock import MagicMock, patch
from python_pkg.steam_backlog_enforcer.store_blocker import (
from steam_backlog_enforcer.store_blocker import (
_block_store_iptables,
_block_via_hosts_install,
_is_iptables_blocked,
@ -27,7 +27,7 @@ class TestIsStoreBlocked:
hosts_file.write_text("0.0.0.0 store.steampowered.com\n", encoding="utf-8")
with (
patch(
"python_pkg.steam_backlog_enforcer.store_blocker.HOSTS_FILE",
"steam_backlog_enforcer.store_blocker.HOSTS_FILE",
hosts_file,
),
):
@ -38,11 +38,11 @@ class TestIsStoreBlocked:
hosts_file.write_text("# 0.0.0.0 store.steampowered.com\n", encoding="utf-8")
with (
patch(
"python_pkg.steam_backlog_enforcer.store_blocker.HOSTS_FILE",
"steam_backlog_enforcer.store_blocker.HOSTS_FILE",
hosts_file,
),
patch(
"python_pkg.steam_backlog_enforcer.store_blocker._is_iptables_blocked",
"steam_backlog_enforcer.store_blocker._is_iptables_blocked",
return_value=False,
),
):
@ -53,11 +53,11 @@ class TestIsStoreBlocked:
hosts_file.write_text("127.0.0.1 localhost\n", encoding="utf-8")
with (
patch(
"python_pkg.steam_backlog_enforcer.store_blocker.HOSTS_FILE",
"steam_backlog_enforcer.store_blocker.HOSTS_FILE",
hosts_file,
),
patch(
"python_pkg.steam_backlog_enforcer.store_blocker._is_iptables_blocked",
"steam_backlog_enforcer.store_blocker._is_iptables_blocked",
return_value=True,
),
):
@ -67,11 +67,11 @@ class TestIsStoreBlocked:
hosts_file = tmp_path / "nonexistent"
with (
patch(
"python_pkg.steam_backlog_enforcer.store_blocker.HOSTS_FILE",
"steam_backlog_enforcer.store_blocker.HOSTS_FILE",
hosts_file,
),
patch(
"python_pkg.steam_backlog_enforcer.store_blocker._is_iptables_blocked",
"steam_backlog_enforcer.store_blocker._is_iptables_blocked",
return_value=False,
),
):
@ -82,11 +82,11 @@ class TestIsStoreBlocked:
hosts_file.write_text("127.0.0.1 store.steampowered.com\n", encoding="utf-8")
with (
patch(
"python_pkg.steam_backlog_enforcer.store_blocker.HOSTS_FILE",
"steam_backlog_enforcer.store_blocker.HOSTS_FILE",
hosts_file,
),
patch(
"python_pkg.steam_backlog_enforcer.store_blocker._is_iptables_blocked",
"steam_backlog_enforcer.store_blocker._is_iptables_blocked",
return_value=False,
),
):
@ -98,7 +98,7 @@ class TestBlockStore:
def test_already_blocked(self) -> None:
with patch(
"python_pkg.steam_backlog_enforcer.store_blocker.is_store_blocked",
"steam_backlog_enforcer.store_blocker.is_store_blocked",
return_value=True,
):
assert block_store() is True
@ -106,18 +106,18 @@ class TestBlockStore:
def test_reblock_succeeds(self) -> None:
with (
patch(
"python_pkg.steam_backlog_enforcer.store_blocker.is_store_blocked",
"steam_backlog_enforcer.store_blocker.is_store_blocked",
side_effect=[False, True],
),
patch(
"python_pkg.steam_backlog_enforcer.store_blocker._reblock_hosts",
"steam_backlog_enforcer.store_blocker._reblock_hosts",
return_value=True,
),
patch(
"python_pkg.steam_backlog_enforcer.store_blocker._block_store_iptables",
"steam_backlog_enforcer.store_blocker._block_store_iptables",
),
patch(
"python_pkg.steam_backlog_enforcer.store_blocker.flush_dns_cache",
"steam_backlog_enforcer.store_blocker.flush_dns_cache",
),
):
assert block_store() is True
@ -125,23 +125,23 @@ class TestBlockStore:
def test_fallback_to_install_script(self) -> None:
with (
patch(
"python_pkg.steam_backlog_enforcer.store_blocker.is_store_blocked",
"steam_backlog_enforcer.store_blocker.is_store_blocked",
side_effect=[False, False],
),
patch(
"python_pkg.steam_backlog_enforcer.store_blocker._reblock_hosts",
"steam_backlog_enforcer.store_blocker._reblock_hosts",
return_value=False,
),
patch(
"python_pkg.steam_backlog_enforcer.store_blocker._block_via_hosts_install",
"steam_backlog_enforcer.store_blocker._block_via_hosts_install",
return_value=True,
),
patch(
"python_pkg.steam_backlog_enforcer.store_blocker._block_store_iptables",
"steam_backlog_enforcer.store_blocker._block_store_iptables",
return_value=False,
),
patch(
"python_pkg.steam_backlog_enforcer.store_blocker.flush_dns_cache",
"steam_backlog_enforcer.store_blocker.flush_dns_cache",
),
):
assert block_store() is True
@ -149,19 +149,19 @@ class TestBlockStore:
def test_all_fail(self) -> None:
with (
patch(
"python_pkg.steam_backlog_enforcer.store_blocker.is_store_blocked",
"steam_backlog_enforcer.store_blocker.is_store_blocked",
side_effect=[False, False],
),
patch(
"python_pkg.steam_backlog_enforcer.store_blocker._reblock_hosts",
"steam_backlog_enforcer.store_blocker._reblock_hosts",
return_value=False,
),
patch(
"python_pkg.steam_backlog_enforcer.store_blocker._block_via_hosts_install",
"steam_backlog_enforcer.store_blocker._block_via_hosts_install",
return_value=False,
),
patch(
"python_pkg.steam_backlog_enforcer.store_blocker._block_store_iptables",
"steam_backlog_enforcer.store_blocker._block_store_iptables",
return_value=False,
),
):
@ -170,23 +170,23 @@ class TestBlockStore:
def test_iptables_only_succeeds(self) -> None:
with (
patch(
"python_pkg.steam_backlog_enforcer.store_blocker.is_store_blocked",
"steam_backlog_enforcer.store_blocker.is_store_blocked",
side_effect=[False, False],
),
patch(
"python_pkg.steam_backlog_enforcer.store_blocker._reblock_hosts",
"steam_backlog_enforcer.store_blocker._reblock_hosts",
return_value=False,
),
patch(
"python_pkg.steam_backlog_enforcer.store_blocker._block_via_hosts_install",
"steam_backlog_enforcer.store_blocker._block_via_hosts_install",
return_value=False,
),
patch(
"python_pkg.steam_backlog_enforcer.store_blocker._block_store_iptables",
"steam_backlog_enforcer.store_blocker._block_store_iptables",
return_value=True,
),
patch(
"python_pkg.steam_backlog_enforcer.store_blocker.flush_dns_cache",
"steam_backlog_enforcer.store_blocker.flush_dns_cache",
),
):
assert block_store() is True
@ -197,7 +197,7 @@ class TestBlockViaHostsInstall:
def test_already_blocked(self) -> None:
with patch(
"python_pkg.steam_backlog_enforcer.store_blocker.is_store_blocked",
"steam_backlog_enforcer.store_blocker.is_store_blocked",
return_value=True,
):
assert _block_via_hosts_install() is True
@ -205,11 +205,11 @@ class TestBlockViaHostsInstall:
def test_script_missing(self, tmp_path: Path) -> None:
with (
patch(
"python_pkg.steam_backlog_enforcer.store_blocker.is_store_blocked",
"steam_backlog_enforcer.store_blocker.is_store_blocked",
return_value=False,
),
patch(
"python_pkg.steam_backlog_enforcer.store_blocker.HOSTS_INSTALL_SCRIPT",
"steam_backlog_enforcer.store_blocker.HOSTS_INSTALL_SCRIPT",
tmp_path / "nonexistent.sh",
),
):
@ -221,15 +221,15 @@ class TestBlockViaHostsInstall:
mock_result = MagicMock(returncode=0)
with (
patch(
"python_pkg.steam_backlog_enforcer.store_blocker.is_store_blocked",
"steam_backlog_enforcer.store_blocker.is_store_blocked",
return_value=False,
),
patch(
"python_pkg.steam_backlog_enforcer.store_blocker.HOSTS_INSTALL_SCRIPT",
"steam_backlog_enforcer.store_blocker.HOSTS_INSTALL_SCRIPT",
script,
),
patch(
"python_pkg.steam_backlog_enforcer.store_blocker.subprocess.run",
"steam_backlog_enforcer.store_blocker.subprocess.run",
return_value=mock_result,
),
):
@ -241,15 +241,15 @@ class TestBlockViaHostsInstall:
mock_result = MagicMock(returncode=1, stderr="error", stdout="")
with (
patch(
"python_pkg.steam_backlog_enforcer.store_blocker.is_store_blocked",
"steam_backlog_enforcer.store_blocker.is_store_blocked",
return_value=False,
),
patch(
"python_pkg.steam_backlog_enforcer.store_blocker.HOSTS_INSTALL_SCRIPT",
"steam_backlog_enforcer.store_blocker.HOSTS_INSTALL_SCRIPT",
script,
),
patch(
"python_pkg.steam_backlog_enforcer.store_blocker.subprocess.run",
"steam_backlog_enforcer.store_blocker.subprocess.run",
return_value=mock_result,
),
):
@ -261,15 +261,15 @@ class TestBlockViaHostsInstall:
mock_result = MagicMock(returncode=1, stderr="", stdout="out")
with (
patch(
"python_pkg.steam_backlog_enforcer.store_blocker.is_store_blocked",
"steam_backlog_enforcer.store_blocker.is_store_blocked",
return_value=False,
),
patch(
"python_pkg.steam_backlog_enforcer.store_blocker.HOSTS_INSTALL_SCRIPT",
"steam_backlog_enforcer.store_blocker.HOSTS_INSTALL_SCRIPT",
script,
),
patch(
"python_pkg.steam_backlog_enforcer.store_blocker.subprocess.run",
"steam_backlog_enforcer.store_blocker.subprocess.run",
return_value=mock_result,
),
):
@ -280,15 +280,15 @@ class TestBlockViaHostsInstall:
script.touch()
with (
patch(
"python_pkg.steam_backlog_enforcer.store_blocker.is_store_blocked",
"steam_backlog_enforcer.store_blocker.is_store_blocked",
return_value=False,
),
patch(
"python_pkg.steam_backlog_enforcer.store_blocker.HOSTS_INSTALL_SCRIPT",
"steam_backlog_enforcer.store_blocker.HOSTS_INSTALL_SCRIPT",
script,
),
patch(
"python_pkg.steam_backlog_enforcer.store_blocker.subprocess.run",
"steam_backlog_enforcer.store_blocker.subprocess.run",
side_effect=OSError,
),
):
@ -301,7 +301,7 @@ class TestIsIptablesBlocked:
def test_blocked(self) -> None:
mock_result = MagicMock(returncode=0, stdout="DROP blah")
with patch(
"python_pkg.steam_backlog_enforcer.store_blocker.subprocess.run",
"steam_backlog_enforcer.store_blocker.subprocess.run",
return_value=mock_result,
):
assert _is_iptables_blocked() is True
@ -309,7 +309,7 @@ class TestIsIptablesBlocked:
def test_not_blocked_no_drop(self) -> None:
mock_result = MagicMock(returncode=0, stdout="ACCEPT")
with patch(
"python_pkg.steam_backlog_enforcer.store_blocker.subprocess.run",
"steam_backlog_enforcer.store_blocker.subprocess.run",
return_value=mock_result,
):
assert _is_iptables_blocked() is False
@ -317,14 +317,14 @@ class TestIsIptablesBlocked:
def test_not_blocked_error(self) -> None:
mock_result = MagicMock(returncode=1, stdout="")
with patch(
"python_pkg.steam_backlog_enforcer.store_blocker.subprocess.run",
"steam_backlog_enforcer.store_blocker.subprocess.run",
return_value=mock_result,
):
assert _is_iptables_blocked() is False
def test_os_error(self) -> None:
with patch(
"python_pkg.steam_backlog_enforcer.store_blocker.subprocess.run",
"steam_backlog_enforcer.store_blocker.subprocess.run",
side_effect=OSError,
):
assert _is_iptables_blocked() is False
@ -337,11 +337,11 @@ class TestBlockStoreIptables:
mock_result = MagicMock(returncode=0)
with (
patch(
"python_pkg.steam_backlog_enforcer.store_blocker.subprocess.run",
"steam_backlog_enforcer.store_blocker.subprocess.run",
return_value=mock_result,
),
patch(
"python_pkg.steam_backlog_enforcer.store_blocker.socket.getaddrinfo",
"steam_backlog_enforcer.store_blocker.socket.getaddrinfo",
return_value=[
(None, None, None, None, ("1.2.3.4", 443)),
],
@ -351,7 +351,7 @@ class TestBlockStoreIptables:
def test_os_error(self) -> None:
with patch(
"python_pkg.steam_backlog_enforcer.store_blocker.subprocess.run",
"steam_backlog_enforcer.store_blocker.subprocess.run",
side_effect=OSError,
):
assert _block_store_iptables() is False
@ -362,11 +362,11 @@ class TestBlockStoreIptables:
mock_result = MagicMock(returncode=0)
with (
patch(
"python_pkg.steam_backlog_enforcer.store_blocker.subprocess.run",
"steam_backlog_enforcer.store_blocker.subprocess.run",
return_value=mock_result,
),
patch(
"python_pkg.steam_backlog_enforcer.store_blocker.socket.getaddrinfo",
"steam_backlog_enforcer.store_blocker.socket.getaddrinfo",
side_effect=socket.gaierror,
),
):
@ -390,11 +390,11 @@ class TestBlockStoreIptables:
with (
patch(
"python_pkg.steam_backlog_enforcer.store_blocker.subprocess.run",
"steam_backlog_enforcer.store_blocker.subprocess.run",
side_effect=side_effect,
),
patch(
"python_pkg.steam_backlog_enforcer.store_blocker.socket.getaddrinfo",
"steam_backlog_enforcer.store_blocker.socket.getaddrinfo",
side_effect=__import__("socket").gaierror,
),
):
@ -407,15 +407,15 @@ class TestUnblockStore:
def test_both_succeed(self) -> None:
with (
patch(
"python_pkg.steam_backlog_enforcer.store_blocker._unblock_store_iptables",
"steam_backlog_enforcer.store_blocker._unblock_store_iptables",
return_value=True,
),
patch(
"python_pkg.steam_backlog_enforcer.store_blocker._unblock_hosts",
"steam_backlog_enforcer.store_blocker._unblock_hosts",
return_value=True,
),
patch(
"python_pkg.steam_backlog_enforcer.store_blocker.flush_dns_cache",
"steam_backlog_enforcer.store_blocker.flush_dns_cache",
),
):
assert unblock_store() is True
@ -423,15 +423,15 @@ class TestUnblockStore:
def test_iptables_fails(self) -> None:
with (
patch(
"python_pkg.steam_backlog_enforcer.store_blocker._unblock_store_iptables",
"steam_backlog_enforcer.store_blocker._unblock_store_iptables",
return_value=False,
),
patch(
"python_pkg.steam_backlog_enforcer.store_blocker._unblock_hosts",
"steam_backlog_enforcer.store_blocker._unblock_hosts",
return_value=True,
),
patch(
"python_pkg.steam_backlog_enforcer.store_blocker.flush_dns_cache",
"steam_backlog_enforcer.store_blocker.flush_dns_cache",
),
):
assert unblock_store() is True
@ -439,15 +439,15 @@ class TestUnblockStore:
def test_both_fail(self) -> None:
with (
patch(
"python_pkg.steam_backlog_enforcer.store_blocker._unblock_store_iptables",
"steam_backlog_enforcer.store_blocker._unblock_store_iptables",
return_value=False,
),
patch(
"python_pkg.steam_backlog_enforcer.store_blocker._unblock_hosts",
"steam_backlog_enforcer.store_blocker._unblock_hosts",
return_value=False,
),
patch(
"python_pkg.steam_backlog_enforcer.store_blocker.flush_dns_cache",
"steam_backlog_enforcer.store_blocker.flush_dns_cache",
),
):
assert unblock_store() is False
@ -458,13 +458,13 @@ class TestUnblockStoreIptables:
def test_success(self) -> None:
with patch(
"python_pkg.steam_backlog_enforcer.store_blocker.subprocess.run",
"steam_backlog_enforcer.store_blocker.subprocess.run",
):
assert _unblock_store_iptables() is True
def test_os_error(self) -> None:
with patch(
"python_pkg.steam_backlog_enforcer.store_blocker.subprocess.run",
"steam_backlog_enforcer.store_blocker.subprocess.run",
side_effect=OSError,
):
assert _unblock_store_iptables() is False

View File

@ -5,7 +5,7 @@ from __future__ import annotations
from typing import TYPE_CHECKING
from unittest.mock import MagicMock, patch
from python_pkg.steam_backlog_enforcer.store_blocker import (
from steam_backlog_enforcer.store_blocker import (
_disable_hosts_protection,
_enable_hosts_protection,
_reblock_hosts,
@ -17,7 +17,7 @@ from python_pkg.steam_backlog_enforcer.store_blocker import (
if TYPE_CHECKING:
from pathlib import Path
PKG = "python_pkg.steam_backlog_enforcer.store_blocker"
PKG = "steam_backlog_enforcer.store_blocker"
class TestSudoWriteHosts:

View File

@ -8,7 +8,7 @@ from unittest.mock import patch
import pytest
from python_pkg.steam_backlog_enforcer._whitelist import (
from steam_backlog_enforcer._whitelist import (
WHITELIST_COOLDOWN_SECONDS,
_append_audit_log,
_load_approved,
@ -170,7 +170,7 @@ class TestLockAndUnlock:
with (
patch(
"python_pkg.steam_backlog_enforcer._whitelist.APPROVED_EXCEPTIONS_FILE",
"steam_backlog_enforcer._whitelist.APPROVED_EXCEPTIONS_FILE",
approved,
),
patch("shutil.which", return_value="/usr/bin/chattr"),
@ -206,7 +206,7 @@ class TestPersistence:
bad = tmp_path / "pending.json"
bad.write_text("not json{{", encoding="utf-8")
with patch(
"python_pkg.steam_backlog_enforcer._whitelist.PENDING_EXCEPTIONS_FILE",
"steam_backlog_enforcer._whitelist.PENDING_EXCEPTIONS_FILE",
bad,
):
assert _load_pending() == []
@ -215,7 +215,7 @@ class TestPersistence:
bad = tmp_path / "pending.json"
bad.write_text('{"key": "value"}', encoding="utf-8")
with patch(
"python_pkg.steam_backlog_enforcer._whitelist.PENDING_EXCEPTIONS_FILE",
"steam_backlog_enforcer._whitelist.PENDING_EXCEPTIONS_FILE",
bad,
):
assert _load_pending() == []
@ -234,7 +234,7 @@ class TestPersistence:
bad = tmp_path / "approved.json"
bad.write_text("{{broken", encoding="utf-8")
with patch(
"python_pkg.steam_backlog_enforcer._whitelist.APPROVED_EXCEPTIONS_FILE",
"steam_backlog_enforcer._whitelist.APPROVED_EXCEPTIONS_FILE",
bad,
):
assert _load_approved() == []
@ -243,7 +243,7 @@ class TestPersistence:
bad = tmp_path / "approved.json"
bad.write_text('"just a string"', encoding="utf-8")
with patch(
"python_pkg.steam_backlog_enforcer._whitelist.APPROVED_EXCEPTIONS_FILE",
"steam_backlog_enforcer._whitelist.APPROVED_EXCEPTIONS_FILE",
bad,
):
assert _load_approved() == []
@ -268,7 +268,7 @@ class TestAppendAuditLog:
def test_audit_log_written(self, tmp_path: Path) -> None:
log_file = tmp_path / "audit.log"
with patch(
"python_pkg.steam_backlog_enforcer._whitelist.EXCEPTION_AUDIT_LOG",
"steam_backlog_enforcer._whitelist.EXCEPTION_AUDIT_LOG",
log_file,
):
_append_audit_log(440, "some reason", "REQUESTED")
@ -280,7 +280,7 @@ class TestAppendAuditLog:
def test_audit_log_appends(self, tmp_path: Path) -> None:
log_file = tmp_path / "audit.log"
with patch(
"python_pkg.steam_backlog_enforcer._whitelist.EXCEPTION_AUDIT_LOG",
"steam_backlog_enforcer._whitelist.EXCEPTION_AUDIT_LOG",
log_file,
):
_append_audit_log(440, "first", "REQUESTED")