"""Attended controller transactions against disposable files and fake host boundaries.""" import base64 import fcntl import importlib.util import json import os from contextlib import contextmanager from pathlib import Path from types import SimpleNamespace import pytest from tests import test_backup_install as fixtures bundle = fixtures.bundle install = fixtures.install prepared = fixtures.prepared ROOT = Path(__file__).parents[1] spec = importlib.util.spec_from_file_location( "backup_install_runtime", ROOT / "scripts/backup_install_runtime.py" ) runtime = importlib.util.module_from_spec(spec) spec.loader.exec_module(runtime) @pytest.fixture def controller(prepared, monkeypatch): root, manifest, contents, before, _ = prepared archive, manifest_hash = bundle() source = (ROOT / "scripts/backup_install.py").read_bytes() route = b"# synthetic verifier\n" plan = dict( attempt_id="20261031bacc0001", cluster_uid=runtime.CLUSTER_UID, archive_sha256=runtime.sha(archive), manifest_sha256=manifest_hash, capacity_profile=runtime.PROFILE, expected_before=before, installer_sha256=runtime.sha(source), route_verifier_sha256=runtime.sha(route), route_execution_digest="sha256:" + "a" * 64, route_packet={"execution_digest": "sha256:" + "a" * 64}, ) packet = dict( plan=plan, proposal_digest="sha256:" + runtime.sha(runtime.canonical(plan)), installer=base64.b64encode(source).decode(), archive=base64.b64encode(archive).decode(), route_verifier=base64.b64encode(route).decode(), ) monkeypatch.setattr(runtime, "ROOT", root) monkeypatch.setattr(runtime, "ATTEMPTS", root / "var/lib/install-attempts") # Controller owner guard is synthetic; filesystem library still sees real ownership. fake_os = SimpleNamespace(**vars(os)) fake_os.geteuid = lambda: 0 monkeypatch.setattr(runtime, "os", fake_os) calls = [] @contextmanager def locked(): calls.append("lock") try: yield finally: calls.append("unlock") monkeypatch.setattr(runtime, "locks", locked) monkeypatch.setattr(runtime, "cluster_identity", lambda: calls.append("cluster")) monkeypatch.setattr(runtime, "inactive_units", lambda: calls.append("inactive") or {}) monkeypatch.setattr(runtime, "capacity", lambda _: {"fixture": True}) monkeypatch.setattr(runtime, "runtime_dependencies", lambda: {"fixture": True}) monkeypatch.setattr( runtime, "route_verified", lambda *_: calls.append("route") or {"helm_revision": 6} ) def command(args, **kwargs): assert args == ["systemctl", "daemon-reload"] calls.append("reload") return b"" monkeypatch.setattr(runtime, "command", command) return packet, root, calls def test_preflight_holds_both_locks_without_writing(controller): packet, root, calls = controller result = runtime.run(packet, "preflight") assert result["status"] == "preflight_verified" assert install.observe(root) == packet["plan"]["expected_before"] assert not runtime.ATTEMPTS.exists() assert calls[0] == "lock" and calls[-1] == "unlock" assert "reload" not in calls def test_install_and_explicit_rollback_use_durable_receipts(controller): packet, root, calls = controller result = runtime.run(packet, "install") assert result["status"] == "sources_installed_units_inactive_verified" assert result["verified_targets"] == 19 assert result["timers_activated"] is False and result["capture_executed"] is False directory = runtime.ATTEMPTS / packet["plan"]["attempt_id"] assert json.loads((directory / "receipt.json").read_text()) == result assert (directory / "files/receipt.json").is_file() assert calls.count("reload") == 1 with pytest.raises(runtime.Refused): runtime.run(packet, "install") restored = runtime.run(packet, "rollback") assert restored["status"] == "prior_files_restored_units_inactive_verified" now = install.observe(root) for target, before in packet["plan"]["expected_before"].items(): assert now[target]["exists"] == before["exists"] if before["exists"]: assert all(now[target][key] == before[key] for key in ("sha256", "mode", "uid", "gid")) assert calls.count("reload") == 2 @pytest.mark.parametrize("boundary", ["cluster", "route", "capacity", "units", "sdk"]) def test_missing_prerequisite_refuses_before_attempt_and_replacement( controller, monkeypatch, boundary ): packet, root, _ = controller names = dict( cluster="cluster_identity", route="route_verified", capacity="capacity", units="inactive_units", sdk="runtime_dependencies", ) def refuse(*_): raise runtime.Refused("synthetic_refusal") monkeypatch.setattr(runtime, names[boundary], refuse) with pytest.raises(runtime.Refused): runtime.run(packet, "install") assert not runtime.ATTEMPTS.exists() assert install.observe(root) == packet["plan"]["expected_before"] def test_changed_bytes_or_digest_refuse_before_locks(controller): packet, _, calls = controller packet["installer"] = base64.b64encode(b"raise RuntimeError('must not execute')").decode() with pytest.raises(runtime.Refused, match="source_digest_differs"): runtime.run(packet, "install") assert calls == [] def test_failed_reload_keeps_preimages_and_requires_explicit_rollback(controller, monkeypatch): packet, _, _ = controller def fail(*_, **__): raise RuntimeError("synthetic provider text") monkeypatch.setattr(runtime, "command", fail) with pytest.raises(RuntimeError): runtime.run(packet, "install") directory = runtime.ATTEMPTS / packet["plan"]["attempt_id"] receipt = json.loads((directory / "receipt.json").read_text()) assert receipt["status"] == "installation_requires_inspection" assert "provider text" not in (directory / "receipt.json").read_text() files = json.loads((directory / "files/receipt.json").read_text()) assert files["status"] == "installation_requires_review" assert files["changes"] def test_postflight_route_failure_is_not_installation_success(controller, monkeypatch): packet, _, _ = controller count = 0 def route(*_): nonlocal count count += 1 if count == 2: raise runtime.Refused("route_changed") return {} monkeypatch.setattr(runtime, "route_verified", route) with pytest.raises(runtime.Refused): runtime.run(packet, "install") directory = runtime.ATTEMPTS / packet["plan"]["attempt_id"] assert ( json.loads((directory / "receipt.json").read_text())["status"] == "installation_requires_inspection" ) assert ( json.loads((directory / "files/receipt.json").read_text())["status"] == "sources_installed_units_inactive_verified" ) def test_unit_guard_rejects_enabled_failed_or_foreign_units(monkeypatch): def check(**changes): row = dict( LoadState="loaded", ActiveState="inactive", SubState="dead", UnitFileState="disabled" ) row.update(changes) def command(args, **_): if "list-jobs" in args: return b"" if "list-units" in args: return b"[]" fragment = args[2] if "@" not in args[2] else "coulomb-social-backup-alert@.service" values = dict(row, FragmentPath="/etc/systemd/system/" + fragment) if changes.get("FragmentPath"): values["FragmentPath"] = changes["FragmentPath"] return "\n".join(k + "=" + v for k, v in values.items()).encode() monkeypatch.setattr(runtime, "command", command) return runtime.inactive_units() assert len(check()) == 5 for change in [ dict(ActiveState="active"), dict(UnitFileState="enabled"), dict(LoadState="error"), dict(FragmentPath="/tmp/foreign.service"), ]: with pytest.raises(runtime.Refused): check(**change) def test_launcher_refuses_nonterminal_without_ssh(monkeypatch): spec = importlib.util.spec_from_file_location( "install_backup_launcher", ROOT / "scripts/install-backup-sources.py" ) launcher = importlib.util.module_from_spec(spec) spec.loader.exec_module(launcher) monkeypatch.setattr(launcher, "prepare", lambda: ({"proposal_digest": "fixture"}, "")) monkeypatch.setattr(launcher.sys, "stdin", SimpleNamespace(isatty=lambda: False)) monkeypatch.setattr( launcher.subprocess, "run", lambda *_args, **_kwargs: pytest.fail("SSH forbidden") ) with pytest.raises(SystemExit, match="Founder terminal"): launcher.main(["--execute", "--expect-digest", "fixture"]) def test_real_locks_are_exclusive_preserved_and_released_on_partial_acquire(tmp_path, monkeypatch): state = tmp_path / "state" state.mkdir(mode=0o700) paths = [state / name for name in ("backup.lock", "snapshot.lock")] for path in paths: path.write_bytes(b"retained lock bytes") path.chmod(0o600) before = [install.fingerprint(p) for p in paths] monkeypatch.setattr(runtime, "STATE", state) with runtime.locks(): for path in paths: with path.open("rb") as stream: with pytest.raises(BlockingIOError): fcntl.flock(stream, fcntl.LOCK_EX | fcntl.LOCK_NB) with paths[1].open("rb") as busy: fcntl.flock(busy, fcntl.LOCK_EX | fcntl.LOCK_NB) with pytest.raises(BlockingIOError), runtime.locks(): pytest.fail("second lock must refuse") with paths[0].open("rb") as first: fcntl.flock(first, fcntl.LOCK_EX | fcntl.LOCK_NB) assert [install.fingerprint(p) for p in paths] == before def test_lock_symlink_refuses_without_replacement(tmp_path, monkeypatch): state = tmp_path / "state" state.mkdir(mode=0o700) target = state / "unrelated" target.write_bytes(b"unchanged") (state / "backup.lock").symlink_to(target) monkeypatch.setattr(runtime, "STATE", state) with pytest.raises(OSError), runtime.locks(): pytest.fail("symlink lock must refuse") assert (state / "backup.lock").is_symlink() and target.read_bytes() == b"unchanged" def test_queued_start_refuses_even_before_inactive_unit_checks(monkeypatch): calls = [] def command(args, **_): calls.append(args) return b"123 coulomb-social-paired-backup.service start waiting\n" monkeypatch.setattr(runtime, "command", command) with pytest.raises(runtime.Refused, match="queued_coulomb_unit_job"): runtime.inactive_units() assert len(calls) == 1