78 lines
3.5 KiB
Python
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") == "{}"
|