steam-backlog-enforcer/steam_backlog_enforcer/tests/test_pacman_install.py
Krzysztof kuhy Rudnicki 5d5aa3e773
Some checks are pending
dependency freshness / dependencies on newest stable (push) Waiting to run
md-naming / markdown naming convention (push) Waiting to run
pre-commit / pre-commit (${{ matrix.group }}) (mypy, mypy, ) (push) Waiting to run
pre-commit / pre-commit (${{ matrix.group }}) (pylint, pylint, ) (push) Waiting to run
pre-commit / pre-commit (${{ matrix.group }}) (rest, , mypy,pylint) (push) Waiting to run
Tests / test (push) Waiting to run
Tests / web-coverage (push) Waiting to run
repo-contract / repo contract (push) Waiting to run
Add Librarian-only research window to total block
kuhy 2026-11-11: game-design research needs Librarian (app 4197610)
actually played during the 6-day total block. New root command
total-block-research --until T opens ONE window per lock: <= 48 h,
never past the lock end, a rerun never extends it.

The command only validates and records (/var/lib/steam-backlog-
enforcer, root 0700; lock id + deadline + two latches). The daemon
opens and closes everything: Steam hosts names, the store block, the
total-block chain (IP cache kept) and the steam package, reinstalled
with only guard-lib's package-block hook masked for that one pacman
call (--hookdir, proved in a guest). Lock file, hook and end date are
never touched. Browser-game sites stay null-routed.

Only Librarian may run: loop-local allowed set, strict mode (no
whitelist exceptions, no zero-achievement games), launchers and
counted games killed; state.json is never read or written.

Expiry latches the record, then the ordinary tick re-applies hosts
(now reconciled each tick), chain, uninstall and purge; the normal
store block is restored once, unconditionally.

Failure modes: missing/corrupt/unparseable record = no window and a
corrupt one also refuses a new window (fail closed); a clock set
back cannot reopen a latched window.

Checked: 3290 tests at 100% coverage; whole-tree pylint 10/10 run in
three parts (the hook exceeds the 60 s cap, so SKIP=pylint here); vmbox
guest with real guardctl package-block and the real unit, window
opened, Librarian-only enforced, clock moved past the deadline and the
full block re-applied. Not verified on the host.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WzxdWWzuCEg1m87jpKyRrw
2026-11-11 14:29:58 +01:00

89 lines
3.4 KiB
Python

"""install_package: pacman -S with named hooks masked for that one call."""
from __future__ import annotations
import logging
from pathlib import Path
import subprocess
from unittest.mock import MagicMock, patch
import pytest
from steam_backlog_enforcer import _pacman
PKG = "steam_backlog_enforcer._pacman"
HOOK = "guard-lib-block-steam-block.hook"
def _hookdirs(argv: list[str]) -> list[str]:
"""The values of every --hookdir in *argv*, in order."""
return [argv[i + 1] for i, arg in enumerate(argv) if arg == "--hookdir"]
class TestInstallPackage:
"""The command line, the mask directory and the outcomes."""
def test_plain_install_without_masks(self) -> None:
"""Test that no mask means no --hookdir at all (all hooks run)."""
run = MagicMock(return_value=MagicMock(returncode=0, stderr=""))
with patch(f"{PKG}.subprocess.run", run):
assert _pacman.install_package("steam") is True
argv = run.call_args.args[0]
assert argv[1:] == [_pacman.PACMAN, "-S", "--needed", "--noconfirm", "steam"]
def test_masked_hook_is_a_dev_null_symlink_in_the_last_hookdir(self) -> None:
"""Test that the mask wins and every other hook directory is kept."""
seen: dict[str, object] = {}
def fake_run(argv: list[str], **_kw: object) -> MagicMock:
dirs = _hookdirs(argv)
mask = Path(dirs[-1])
seen["dirs"] = dirs
seen["entries"] = {p.name: p.readlink() for p in mask.iterdir()}
seen["mask"] = mask
return MagicMock(returncode=0, stderr="")
with patch(f"{PKG}.subprocess.run", side_effect=fake_run):
assert _pacman.install_package("steam", masked_hooks=(HOOK,)) is True
dirs = seen["dirs"]
assert isinstance(dirs, list)
assert dirs[:2] == ["/usr/share/libalpm/hooks", "/etc/pacman.d/hooks"]
assert seen["entries"] == {HOOK: Path("/dev/null")}
mask = seen["mask"]
assert isinstance(mask, Path)
assert not mask.exists()
def test_nonzero_exit_is_false_and_logged(
self, caplog: pytest.LogCaptureFixture
) -> None:
"""Test that a failed transaction is reported with pacman's stderr."""
run = MagicMock(return_value=MagicMock(returncode=1, stderr="boom 404"))
with (
patch(f"{PKG}.subprocess.run", run),
caplog.at_level(logging.ERROR),
):
assert _pacman.install_package("steam", masked_hooks=(HOOK,)) is False
assert "boom 404" in caplog.text
@pytest.mark.parametrize(
"error", [OSError("no sudo"), subprocess.TimeoutExpired("pacman", 1)]
)
def test_launch_errors_are_false(self, error: Exception) -> None:
"""Test that a missing binary or a timeout never raises."""
with patch(f"{PKG}.subprocess.run", side_effect=error):
assert _pacman.install_package("steam") is False
def test_mask_dir_is_removed_even_when_pacman_fails(self) -> None:
"""Test that no temp directory outlives a failed install."""
masks: list[Path] = []
def fake_run(argv: list[str], **_kw: object) -> MagicMock:
masks.append(Path(_hookdirs(argv)[-1]))
gone = "gone"
raise OSError(gone)
with patch(f"{PKG}.subprocess.run", side_effect=fake_run):
assert _pacman.install_package("steam", masked_hooks=(HOOK,)) is False
assert masks
assert not masks[0].exists()