mirror of
https://github.com/kuhyx/steam-backlog-enforcer.git
synced 2026-11-11 21:13:48 +01:00
Each of these was one file doing several jobs, so they are split along what they actually do rather than at arbitrary line offsets: - _total_block: the lock file and the status derived from it stay in _total_block; making Steam *not installed* moves to _total_block_purge, making it *unreachable* to _total_block_hosts / _total_block_iptables. The generic pacman verbs (owner, installed, remove) become _pacman, since nothing about them is total-block-specific. Domain and process name tables become data modules. - _playtime: budget state and persistence (_playtime_state), pure transitions (_playtime_budget), cmdline process detection (_playtime_procs), the warn/cutoff/kill ladder (_playtime_cutoff), desktop notifications (_playtime_notify), process-tree walking and killing (_playtime_kill), mountinfo parsing (_playtime_mountinfo) and the shared privileged-command helper (_playtime_run). - _mcp: read-only tools (_mcp_query) split from mutating ones (_mcp_actions, _mcp_gaming_time), so "what can this server change?" is answerable from one file. _mcp keeps the server object and entry point. No re-export shims: callers import from the module that defines the name. That keeps a stale patch target loud (AttributeError) instead of letting it pass while intercepting nothing. `ruff check --select F821,F401` is run after every extraction -- an earlier mechanical attempt moved functions without the module-level names they referenced, which imported fine and only failed when called (349 errors), so it was reverted and redone by hand. The autouse fixtures in conftest are now imported by name and held by __all__; ruff --unsafe-fixes had deleted them as unused, since pytest resolves fixtures by name. 1324 tests pass at 100.00% coverage in the clean venv; `main status` is byte-identical to a worktree at HEAD. 49 -> 39 files over the cap. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PPpmMpTSkp8UziRc5u8B7P
137 lines
5.4 KiB
Python
137 lines
5.4 KiB
Python
"""Tests for the lock file and the status derived from it."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from datetime import datetime, timezone
|
|
import json
|
|
|
|
from steam_backlog_enforcer._total_block import (
|
|
get_total_block_status,
|
|
is_total_block_active,
|
|
total_block_needs_cleanup,
|
|
)
|
|
from steam_backlog_enforcer.tests._total_block_paths import (
|
|
Paths,
|
|
write_lock,
|
|
)
|
|
|
|
PKG = "steam_backlog_enforcer._total_block"
|
|
|
|
_NOW = datetime.now(timezone.utc).timestamp()
|
|
|
|
|
|
class TestIsTotalBlockActive:
|
|
"""Tests for is total block active."""
|
|
|
|
def test_no_lock_file(self) -> None:
|
|
"""Test that no lock file."""
|
|
assert is_total_block_active() is False
|
|
|
|
def test_active_lock(self, total_block_paths: Paths) -> None:
|
|
"""Test that active lock."""
|
|
write_lock(total_block_paths, _NOW, _NOW + 3600)
|
|
assert is_total_block_active() is True
|
|
|
|
def test_expired_lock(self, total_block_paths: Paths) -> None:
|
|
"""Test that expired lock."""
|
|
write_lock(total_block_paths, _NOW - 3600, _NOW - 1)
|
|
assert is_total_block_active() is False
|
|
|
|
def test_malformed_json(self, total_block_paths: Paths) -> None:
|
|
"""Test that malformed json."""
|
|
total_block_paths.lock_file.parent.mkdir(parents=True, exist_ok=True)
|
|
total_block_paths.lock_file.write_text("not json", encoding="utf-8")
|
|
assert is_total_block_active() is False
|
|
|
|
def test_non_dict_json(self, total_block_paths: Paths) -> None:
|
|
"""Test that non dict json."""
|
|
total_block_paths.lock_file.parent.mkdir(parents=True, exist_ok=True)
|
|
total_block_paths.lock_file.write_text("[1, 2, 3]", encoding="utf-8")
|
|
assert is_total_block_active() is False
|
|
|
|
def test_missing_until_key(self, total_block_paths: Paths) -> None:
|
|
"""Test that missing until key."""
|
|
total_block_paths.lock_file.parent.mkdir(parents=True, exist_ok=True)
|
|
total_block_paths.lock_file.write_text(
|
|
json.dumps({"days": 1}), encoding="utf-8"
|
|
)
|
|
assert is_total_block_active() is False
|
|
|
|
def test_non_numeric_until(self, total_block_paths: Paths) -> None:
|
|
"""Test that non numeric until."""
|
|
total_block_paths.lock_file.parent.mkdir(parents=True, exist_ok=True)
|
|
total_block_paths.lock_file.write_text(
|
|
json.dumps({"until": "not-a-number"}), encoding="utf-8"
|
|
)
|
|
assert is_total_block_active() is False
|
|
|
|
|
|
class TestTotalBlockNeedsCleanup:
|
|
"""Tests for total block needs cleanup."""
|
|
|
|
def test_no_lock_file(self) -> None:
|
|
"""Test that no lock file."""
|
|
assert total_block_needs_cleanup() is False
|
|
|
|
def test_active_lock_no_cleanup_needed(self, total_block_paths: Paths) -> None:
|
|
"""Test that active lock no cleanup needed."""
|
|
write_lock(total_block_paths, _NOW, _NOW + 3600)
|
|
assert total_block_needs_cleanup() is False
|
|
|
|
def test_expired_lock_needs_cleanup(self, total_block_paths: Paths) -> None:
|
|
"""Test that expired lock needs cleanup."""
|
|
write_lock(total_block_paths, _NOW - 3600, _NOW - 1)
|
|
assert total_block_needs_cleanup() is True
|
|
|
|
|
|
class TestGetTotalBlockStatus:
|
|
"""Tests for get total block status."""
|
|
|
|
def test_no_lock(self) -> None:
|
|
"""Test that no lock."""
|
|
status = get_total_block_status()
|
|
assert status.active is False
|
|
assert status.started_at is None
|
|
assert status.until is None
|
|
assert status.days == 0
|
|
assert status.days_remaining == 0.0
|
|
|
|
def test_active_lock(self, total_block_paths: Paths) -> None:
|
|
"""Test that active lock."""
|
|
write_lock(total_block_paths, _NOW, _NOW + 86400, days=1)
|
|
status = get_total_block_status()
|
|
assert status.active is True
|
|
assert status.days == 1
|
|
assert 0.0 < status.days_remaining <= 1.0
|
|
assert status.started_at is not None
|
|
assert status.until is not None
|
|
|
|
def test_expired_lock(self, total_block_paths: Paths) -> None:
|
|
"""Test that expired lock."""
|
|
write_lock(total_block_paths, _NOW - 7200, _NOW - 3600, days=1)
|
|
status = get_total_block_status()
|
|
assert status.active is False
|
|
assert status.days_remaining == 0.0
|
|
|
|
def test_malformed_json_returns_inactive(self, total_block_paths: Paths) -> None:
|
|
"""Test that malformed json returns inactive."""
|
|
total_block_paths.lock_file.parent.mkdir(parents=True, exist_ok=True)
|
|
total_block_paths.lock_file.write_text("garbage", encoding="utf-8")
|
|
status = get_total_block_status()
|
|
assert status.active is False
|
|
|
|
def test_non_int_days_defaults_to_zero(self, total_block_paths: Paths) -> None:
|
|
"""Test that non int days defaults to zero."""
|
|
total_block_paths.lock_file.parent.mkdir(parents=True, exist_ok=True)
|
|
total_block_paths.lock_file.write_text(
|
|
json.dumps({"started_at": _NOW, "until": _NOW + 3600, "days": "one"}),
|
|
encoding="utf-8",
|
|
)
|
|
status = get_total_block_status()
|
|
assert status.days == 0
|
|
|
|
|
|
# ──────────────────────────────────────────────────────────────
|
|
# Process killing
|
|
# ──────────────────────────────────────────────────────────────
|