Files
hermes-agent/tests/hermes_cli/test_cmd_uninstall.py
2026-10-10 15:20:13 +02:00

78 lines
3.5 KiB
Python

"""The real uninstall parser must select the data path, never code removal."""
from __future__ import annotations
import argparse
import pytest
from hermes_cli import main, uninstall
from hermes_cli.subcommands.uninstall import build_uninstall_parser
from tests.hermes_cli.test_data_uninstall import layout
@pytest.mark.parametrize("entry", ["cli", "module", "cli-dry-run"])
def test_data_entrypoints_preserve_runtime(layout, monkeypatch, entry):
_, witnesses, data = layout
def no_code_removal(*args, **kwargs):
pytest.fail("data-only dispatch reached code removal")
monkeypatch.setattr(uninstall, "run_uninstall", no_code_removal)
monkeypatch.setattr(uninstall, "run_gui_uninstall", no_code_removal)
monkeypatch.setattr(main, "_require_tty", no_code_removal)
if entry == "module":
assert uninstall.main(["--mode", "data"]) == 0
else:
parser = argparse.ArgumentParser()
build_uninstall_parser(parser.add_subparsers(dest="command"), cmd_uninstall=main.cmd_uninstall)
args = parser.parse_args(["uninstall", "--data", "--dry-run" if entry == "cli-dry-run" else "--yes"])
args.func(args)
assert all(path.exists() for path in witnesses)
assert all(path.exists() is (entry == "cli-dry-run") for path in data)
@pytest.mark.parametrize("other", ["--full", "--gui"])
def test_conflicting_data_mode_is_rejected_before_dispatch(other):
parser = argparse.ArgumentParser()
build_uninstall_parser(parser.add_subparsers(dest="command"), cmd_uninstall=main.cmd_uninstall)
with pytest.raises(SystemExit) as error:
parser.parse_args(["uninstall", "--data", other, "--yes"])
assert error.value.code == 2
def test_module_subprocess_removes_only_the_disposable_home(layout, tmp_path):
import os
import subprocess
import sys
from pathlib import Path
home, witnesses, data = layout
root = Path(uninstall.__file__).resolve().parents[1]
env = {key: value for key, value in os.environ.items() if key.upper() in {
"PATH", "SYSTEMROOT", "WINDIR", "SYSTEMDRIVE", "TEMP", "TMP",
}}
env.update(HERMES_HOME=str(home), HERMES_RUNTIME_DIR=str(home / "machine" / "tool-store"),
HOME=str(tmp_path), USERPROFILE=str(tmp_path), APPDATA=str(tmp_path),
LOCALAPPDATA=str(tmp_path), PYTHONPATH=str(root), PYTHONUTF8="1")
result = subprocess.run([sys.executable, "-m", "hermes_cli.uninstall", "--mode", "data"],
cwd=tmp_path, env=env, stdin=subprocess.DEVNULL,
capture_output=True, text=True, encoding="utf-8", timeout=30, check=False)
assert result.returncode == 0, result.stdout + result.stderr
# This process's actual source is outside the temp home, unlike layout's
# declared source. Managed tools, launchers and sibling profiles survive.
assert all(path.exists() for path in witnesses[1:])
assert all(not path.exists() for path in data)
assert not (home / "skills").exists()
assert not (home / "SOUL.md").exists()
def test_module_uses_the_desktop_instances_explicit_data_path(layout, tmp_path):
_, _, _ = layout
ordinary = tmp_path / "user" / "desktop-data"
variant = tmp_path / "variant-desktop-data"
for directory in (ordinary, variant):
directory.mkdir()
(directory / "preferences.json").write_text("{}", encoding="utf-8")
assert uninstall.main(["--mode", "data", "--desktop-userdata", str(variant)]) == 0
assert not variant.exists()
assert (ordinary / "preferences.json").read_text(encoding="utf-8") == "{}"