"""Run the real handoffs against a disposable CLI, never an installed updater.""" from __future__ import annotations import json import os from pathlib import Path import shlex import subprocess import sys import pytest from tests.installation_launcher_fixture import publish_fixture_launcher SCRIPTS = Path(__file__).resolve().parents[2] / "scripts" / "desktop-update" FAKE_CLI = """ import json import os from pathlib import Path import sys def main(): if '--version' in sys.argv: print('Install directory: ' + os.environ.get('HANDOFF_FOREIGN', str(Path(__file__).resolve().parents[1]))); return 0 if '--help' in sys.argv: print('update options') sys.exit(0) receipt = Path(os.environ['HANDOFF_CAPTURE']) previous = receipt.read_text(encoding='utf-8') if receipt.exists() else '' with receipt.open('a', encoding='utf-8') as stream: stream.write(json.dumps({'argv': sys.argv[1:], 'home': os.environ.get('HERMES_HOME'), 'install_root': os.environ.get('HERMES_INSTALL_ROOT'), 'cwd': os.getcwd()}) + '\\n') print('Desktop build failed') if os.environ.get('HANDOFF_EXIT') else None sys.exit(int(os.environ['HANDOFF_EXIT']) if 'HANDOFF_EXIT' in os.environ else (1 if not previous else 0)) if __name__ == '__main__': main() """ def _run_handoff(tmp_path, target, *, windows=False, inherited_home=True, modern=False, code=0, userbin_only=False, foreign=False): install = tmp_path / "checkout with spaces" if windows and not modern: subprocess.run( [sys.executable, "-m", "venv", "--without-pip", str(install / "venv")], check=True, capture_output=True, timeout=60, ) package = ( install / "venv" / "Lib" / "site-packages" if windows and not modern else install ) / "hermes_cli" package.mkdir(parents=True) (package / "__init__.py").touch() (package / "main.py").write_text(FAKE_CLI, encoding="utf-8") if modern: launcher = publish_fixture_launcher(install, FAKE_CLI) if userbin_only: userbin = tmp_path / '.local/bin' userbin.mkdir(parents=True) launcher.rename(userbin / launcher.name) capture = tmp_path / "calls.jsonl" home = tmp_path / "profile home" if inherited_home else tmp_path home.mkdir(exist_ok=True) env = { **os.environ, "HOME": str(tmp_path), "TMPDIR": str(tmp_path), "HERMES_INSTALL_ROOT": str(install), "HANDOFF_CAPTURE": str(capture), } env.pop("PYTHONPATH", None) env.pop("PYTHONHOME", None) env.pop("HERMES_HOME", None) if inherited_home: env["HERMES_HOME"] = str(home) if modern: env["HANDOFF_EXIT"] = str(code) if foreign: env["HANDOFF_FOREIGN"] = str(tmp_path) env["HERMES_RUNTIME_DIR"] = str(tmp_path / "empty-store") if windows: # The disposable runtime has only the fixture CLI; verification is outside # this transport contract and runs its own harmless fixture implementation. (package / "desktop_update_verify.py").write_text( "def verify_windows_desktop_update(): pass\n", encoding="utf-8", ) command = [ "powershell", "-NoProfile", "-ExecutionPolicy", "Bypass", "-File", str(SCRIPTS / "windows.ps1"), "-InstallRoot", str(install), "-NoUi", ] else: if not modern: bin_dir = install / "venv" / "bin" bin_dir.mkdir(parents=True) (bin_dir / "python3").symlink_to(sys.executable) hermes = bin_dir / "hermes" hermes.write_text( f'#!/usr/bin/env bash\nexec {shlex.quote(sys.executable)} -m hermes_cli.main "$@"\n', encoding="utf-8", ) hermes.chmod(0o755) command = [ "bash", str(SCRIPTS / "posix.sh"), "--install-root", str(install), "--daemonized", "--no-ui", ] result = subprocess.run( [*command, *target], env=env, cwd=tmp_path, capture_output=True, text=True, encoding="utf-8", timeout=90, ) calls = ( [json.loads(line) for line in capture.read_text(encoding="utf-8").splitlines()] if capture.exists() else [] ) return result, calls, home, install def _assert_forwarded( tmp_path, target, expected, *, windows=False, inherited_home=True ): result, calls, home, install = _run_handoff( tmp_path, target, windows=windows, inherited_home=inherited_home, ) assert result.returncode == 0, result.stdout + result.stderr expected_args = ["update", "--yes", "--gateway"] if windows: expected_args += ["--force"] expected_args += expected expected_argvs = [expected_args] * 2 if windows: # Desktop stopped the local gateways before handing off; a verified # update restores the whole fleet in the same home and install. expected_argvs.append(["gateway", "start", "--all"]) assert calls == [ { "argv": argv, "home": str(home), "cwd": str(install), "install_root": str(install), } for argv in expected_argvs ], calls receipt = json.loads( (home / ".hermes-update-result.json").read_text(encoding="utf-8-sig") ) assert receipt["ok"] assert receipt["channel"] == (expected[1] if expected[0] == "--channel" else "") assert not (home / ".hermes-update-in-progress").exists() @pytest.mark.platforms("posix") @pytest.mark.parametrize("channel", ["stable", "canary", "main"]) def test_posix_channel_survives_retry_in_active_profile(tmp_path, channel): _assert_forwarded(tmp_path, ["--channel", channel], ["--channel", channel]) @pytest.mark.platforms("windows") @pytest.mark.parametrize("channel", ["stable", "canary", "main"]) def test_windows_channel_survives_retry_in_active_profile(tmp_path, channel): _assert_forwarded( tmp_path, ["-Channel", channel], ["--channel", channel], windows=True ) @pytest.mark.platforms("posix") @pytest.mark.parametrize( "target, expected", [ ([], ["--branch", "main"]), (["--branch", "feature/target"], ["--branch", "feature/target"]), ], ) def test_posix_legacy_branch_and_default_home(tmp_path, target, expected): _assert_forwarded(tmp_path, target, expected, inherited_home=False) @pytest.mark.platforms("windows") @pytest.mark.parametrize( "target, expected", [ ([], ["--branch", "main"]), (["-Branch", "feature/target"], ["--branch", "feature/target"]), ], ) def test_windows_legacy_branch_and_default_home(tmp_path, target, expected): _assert_forwarded(tmp_path, target, expected, windows=True, inherited_home=False) def _assert_rejected(tmp_path, target, *, windows=False): result, calls, home, _ = _run_handoff(tmp_path, target, windows=windows) assert result.returncode != 0, result.stdout + result.stderr assert not calls assert not (home / ".hermes-update-in-progress").exists() assert not (home / ".hermes-update-result.json").exists() @pytest.mark.platforms("posix") @pytest.mark.parametrize( "target", [ ["--channel", "nightly"], ["--channel", ""], ["--channel"], ["--branch", "main", "--channel", "stable"], ["--channel", "canary", "--branch", "main"], ], ) def test_posix_rejects_invalid_or_conflicting_target_before_update(tmp_path, target): _assert_rejected(tmp_path, target) @pytest.mark.platforms("windows") @pytest.mark.parametrize( "target", [ ["-Channel", "nightly"], ["-Channel", ""], ["-Channel"], ["-Branch", "main", "-Channel", "stable"], ["-Channel", "canary", "-Branch", "main"], ], ) def test_windows_rejects_invalid_or_conflicting_target_before_update(tmp_path, target): _assert_rejected(tmp_path, target, windows=True) @pytest.mark.platforms("posix") @pytest.mark.parametrize("code", [0, 1, 2]) def test_pm_handoff_uses_published_launcher_and_does_not_retry(tmp_path, code): result, calls, home, install = _run_handoff(tmp_path, ["--channel", "canary"], modern=True, code=code) assert result.returncode == code, result.stdout + result.stderr assert calls == [{"argv": ["update", "--yes", "--gateway", "--channel", "canary"], "home": str(home), "cwd": str(install), "install_root": str(install)}] receipt = json.loads((home / ".hermes-update-result.json").read_text()) assert receipt["ok"] == (code == 0) assert not (install / "venv").exists() @pytest.mark.platforms("posix") @pytest.mark.parametrize("foreign", [False, True]) def test_earlier_pm_userbin_publication_requires_exact_source_identity(tmp_path, foreign): result, calls, home, install = _run_handoff(tmp_path, [], modern=True, userbin_only=True, foreign=foreign) assert result.returncode == (3 if foreign else 0), result.stdout + result.stderr assert len(calls) == (0 if foreign else 1) assert not (install / '.hermes/bin/hermes').exists() assert not (install / 'venv').exists()