steam-backlog-enforcer/steam_backlog_enforcer/tests/test_research_net.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

166 lines
6.5 KiB
Python

"""Opening the network and the package for a research window."""
from __future__ import annotations
from typing import TYPE_CHECKING
from unittest.mock import patch
import pytest
from steam_backlog_enforcer import _research_net as net
from steam_backlog_enforcer.tests._research_helpers import make_record
if TYPE_CHECKING:
from collections.abc import Iterator
PKG = "steam_backlog_enforcer._research_net"
class _World:
"""Records every collaborator call in order and answers from knobs."""
def __init__(self) -> None:
self.calls: list[str] = []
self.steam_installed = False
self.chain_present = True
self.store_blocked = True
self.install_ok = True
self.clock = 1000.0
def step(self, name: str, *, result: object = True) -> object:
"""Log *name* and return *result*."""
self.calls.append(name)
return result
@pytest.fixture(name="world")
def _world() -> Iterator[_World]:
"""Patch the network/package collaborators with a call-recording world."""
w = _World()
def hosts(*, steam_open: bool = False) -> bool:
return bool(w.step(f"hosts:{'open' if steam_open else 'full'}"))
def install(package: str, *, masked_hooks: tuple[str, ...] = ()) -> bool:
w.step(f"install:{package}:{','.join(masked_hooks)}")
w.steam_installed = w.steam_installed or w.install_ok
return w.install_ok
with (
patch(f"{PKG}.refresh_total_block_hosts", side_effect=hosts),
patch(f"{PKG}.flush_dns_cache", side_effect=lambda: w.step("flush")),
patch(f"{PKG}.hosts_blocks_store", side_effect=lambda: w.store_blocked),
patch(f"{PKG}.unblock_store", side_effect=lambda: w.step("unblock-store")),
patch(f"{PKG}.total_block_chain_exists", side_effect=lambda: w.chain_present),
patch(
f"{PKG}.release_total_block_iptables",
side_effect=lambda: w.step("release-chain"),
),
patch(f"{PKG}.is_steam_installed", side_effect=lambda: w.steam_installed),
patch(f"{PKG}.install_package", side_effect=install),
patch(f"{PKG}.monotonic", side_effect=lambda: w.clock),
):
yield w
class TestReconcile:
"""One pass makes the network and the package match the open window."""
def test_first_pass_opens_everything_in_order(self, world: _World) -> None:
"""Test that hosts, store, firewall, then the package are opened."""
net.reconcile_open_window(make_record())
assert world.calls == [
"hosts:open",
"flush",
"unblock-store",
"release-chain",
"install:steam:guard-lib-block-steam-block.hook",
]
def test_converged_pass_does_no_work(self, world: _World) -> None:
"""Test that a second pass over an open window forks nothing."""
net.reconcile_open_window(make_record())
world.calls.clear()
world.store_blocked = False
world.chain_present = False
net.reconcile_open_window(make_record())
assert world.calls == ["hosts:open"]
def test_store_lines_that_come_back_are_undone(self, world: _World) -> None:
"""Test that the hourly hosts reinstall cannot re-close the store."""
net.reconcile_open_window(make_record())
world.calls.clear()
world.store_blocked = True
world.chain_present = False
net.reconcile_open_window(make_record())
assert world.calls == ["hosts:open", "unblock-store"]
def test_chain_that_comes_back_is_dropped(self, world: _World) -> None:
"""Test that a rebuilt chain (e.g. by total-block-refresh) is removed."""
net.reconcile_open_window(make_record())
world.calls.clear()
world.store_blocked = False
world.chain_present = True
net.reconcile_open_window(make_record())
assert world.calls == ["hosts:open", "release-chain"]
def test_a_restarted_daemon_redoes_the_one_time_release(
self, world: _World
) -> None:
"""Test that the first pass of a fresh process is a full pass again."""
net.reconcile_open_window(make_record())
net.RELEASED.clear()
world.calls.clear()
net.reconcile_open_window(make_record())
assert "unblock-store" in world.calls
assert "flush" in world.calls
def test_failed_hosts_refresh_is_retried_next_pass(self, world: _World) -> None:
"""Test that the one-time release is not marked done on failure."""
with patch(f"{PKG}.refresh_total_block_hosts", return_value=False):
net.reconcile_open_window(make_record())
assert not net.RELEASED
world.calls.clear()
net.reconcile_open_window(make_record())
assert world.calls[:3] == ["hosts:open", "flush", "unblock-store"]
class TestSteamPackage:
"""Reinstalling Steam, only when missing, never in a tight loop."""
def test_installed_steam_is_left_alone(self, world: _World) -> None:
"""Test that an installed package is never reinstalled."""
world.steam_installed = True
assert net.ensure_steam_installed() is True
assert not [c for c in world.calls if c.startswith("install")]
def test_missing_steam_is_installed_without_the_block_hook(
self, world: _World
) -> None:
"""Test that only the steam-block hook is masked for the install."""
assert net.ensure_steam_installed() is True
assert world.calls == ["install:steam:guard-lib-block-steam-block.hook"]
def test_failed_install_backs_off(self, world: _World) -> None:
"""Test that a failure waits before the next attempt."""
world.install_ok = False
assert net.ensure_steam_installed() is False
world.calls.clear()
world.clock += net.RETRY_AFTER_SECONDS - 1
assert net.ensure_steam_installed() is False
assert world.calls == []
world.clock += 2
assert net.ensure_steam_installed() is False
assert len(world.calls) == 1
def test_pacman_database_lock_defers_the_install(self, world: _World) -> None:
"""Test that a running pacman transaction is not raced."""
net.DB_LOCK_FILE.parent.mkdir(parents=True)
net.DB_LOCK_FILE.write_text("", encoding="utf-8")
assert net.ensure_steam_installed() is False
assert world.calls == []
def test_hook_name_follows_the_package_block_name(self) -> None:
"""Test that the masked hook is guard-lib's file for steam-block."""
assert net.block_hook_name() == "guard-lib-block-steam-block.hook"