Files
wehub-resource-sync 4b6817381b
CI (OpenClaw E2E) / openclaw test (push) Has been cancelled
CI / coverage-report (push) Has been cancelled
CI / test-kubernetes (push) Has been cancelled
CI / should-run-thorough (push) Has been cancelled
CI / test-thorough (cloudwatch-demo) (push) Has been cancelled
CI / test-thorough (flink-ecs) (push) Has been cancelled
CI / test-thorough (upstream-lambda) (push) Has been cancelled
CI / test-thorough (prefect-ecs-fargate) (push) Has been cancelled
Release / build-binaries (zip, opensre.exe, onefile, windows-latest, windows-x64) (push) Has been cancelled
Benchmark image — build + push to ECR (any adapter) / build + push (push) Has been cancelled
CI / quality (ubuntu-latest) (push) Has been cancelled
CI / test (tools-runtime) (push) Has been cancelled
CI / test (e2e-general) (push) Has been cancelled
CI / test (cli-runtime) (push) Has been cancelled
CI / test (e2e-provider-and-openclaw) (push) Has been cancelled
CI / test (integrations-and-misc) (push) Has been cancelled
Release / verify (push) Has been cancelled
Release / build-python-dist (push) Has been cancelled
Release / build-binaries (tar.gz, opensre, onedir, macos-15-intel, darwin-x64) (push) Has been cancelled
Release / build-binaries (tar.gz, opensre, onedir, macos-latest, darwin-arm64) (push) Has been cancelled
Release / build-binaries (tar.gz, opensre, onedir, ubuntu-22.04, linux-x64) (push) Has been cancelled
Release / publish-release (push) Has been cancelled
Release / publish-main-release (push) Has been cancelled
Interactive Shell Live (PR + post-merge) / turn-checks (no-LLM) (push) Has been cancelled
CodeQL / Analyze (python) (push) Has been cancelled
Interactive Shell Live (PR + post-merge) / turn-live shard ${{ matrix.shard_index }} (push) Has been cancelled
Release / prepare (push) Has been cancelled
Release / build-binaries (tar.gz, opensre, onedir, ubuntu-22.04-arm, linux-arm64) (push) Has been cancelled
Synthetic Deterministic Tests / Synthetic offline (deterministic) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:10:45 +08:00

293 lines
9.6 KiB
Python

from __future__ import annotations
from pathlib import Path
from unittest.mock import patch
import pytest
from click.testing import CliRunner
from surfaces.cli.__main__ import cli
from surfaces.cli.lifecycle.uninstall import _remove_path, run_uninstall
def test_remove_path_removes_file(tmp_path: Path) -> None:
f = tmp_path / "file.txt"
f.write_text("data")
ok, err = _remove_path(f)
assert ok is True
assert err is None
assert not f.exists()
def test_remove_path_removes_directory(tmp_path: Path) -> None:
d = tmp_path / "subdir"
d.mkdir()
(d / "child.txt").write_text("x")
ok, err = _remove_path(d)
assert ok is True
assert err is None
assert not d.exists()
def test_remove_path_nonexistent_returns_ok(tmp_path: Path) -> None:
ok, err = _remove_path(tmp_path / "does_not_exist")
assert ok is True
assert err is None
def test_remove_path_removes_broken_symlink(tmp_path: Path) -> None:
link = tmp_path / "broken"
link.symlink_to(tmp_path / "missing")
ok, err = _remove_path(link)
assert ok is True
assert err is None
assert not link.exists()
assert not link.is_symlink()
def test_remove_path_returns_error_on_permission_denied(
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
d = tmp_path / "locked"
d.mkdir()
def _raise(path: str) -> None:
raise OSError("Permission denied")
monkeypatch.setattr("shutil.rmtree", _raise)
ok, err = _remove_path(d)
assert ok is False
assert "Permission denied" in (err or "")
def test_run_uninstall_cancelled_by_user(
monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]
) -> None:
monkeypatch.setattr("surfaces.cli.lifecycle.uninstall._data_dirs", lambda: [])
monkeypatch.setattr("surfaces.cli.lifecycle.uninstall._is_binary_install", lambda: False)
import questionary as _q
monkeypatch.setattr(
_q,
"confirm",
lambda *_args, **_kwargs: type("Q", (), {"ask": lambda _self: False})(),
)
rc = run_uninstall(yes=False)
assert rc == 0
assert "Cancelled" in capsys.readouterr().out
def test_run_uninstall_aborted_by_keyboard_interrupt(
monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]
) -> None:
monkeypatch.setattr("surfaces.cli.lifecycle.uninstall._data_dirs", lambda: [])
monkeypatch.setattr("surfaces.cli.lifecycle.uninstall._is_binary_install", lambda: False)
import questionary as _q
def _raise_interrupt(*a: object, **kw: object) -> object:
raise KeyboardInterrupt
monkeypatch.setattr(_q, "confirm", _raise_interrupt)
rc = run_uninstall(yes=False)
assert rc == 1
assert "Aborted" in capsys.readouterr().out
def test_run_uninstall_skips_missing_dirs(
monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str], tmp_path: Path
) -> None:
missing = tmp_path / "does_not_exist"
monkeypatch.setattr("surfaces.cli.lifecycle.uninstall._data_dirs", lambda: [missing])
monkeypatch.setattr("surfaces.cli.lifecycle.uninstall._is_binary_install", lambda: False)
monkeypatch.setattr("surfaces.cli.lifecycle.uninstall._pip_uninstall", lambda: 0)
rc = run_uninstall(yes=True)
assert rc == 0
out = capsys.readouterr().out
assert "not found" in out
assert "skipped" in out
def test_run_uninstall_removes_existing_dir(
monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str], tmp_path: Path
) -> None:
d = tmp_path / "tracer_home"
d.mkdir()
monkeypatch.setattr("surfaces.cli.lifecycle.uninstall._data_dirs", lambda: [d])
monkeypatch.setattr("surfaces.cli.lifecycle.uninstall._is_binary_install", lambda: False)
monkeypatch.setattr("surfaces.cli.lifecycle.uninstall._pip_uninstall", lambda: 0)
rc = run_uninstall(yes=True)
assert rc == 0
assert not d.exists()
assert "deleted" in capsys.readouterr().out
def test_run_uninstall_pip_success(
monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]
) -> None:
monkeypatch.setattr("surfaces.cli.lifecycle.uninstall._data_dirs", lambda: [])
monkeypatch.setattr("surfaces.cli.lifecycle.uninstall._is_binary_install", lambda: False)
monkeypatch.setattr("surfaces.cli.lifecycle.uninstall._pip_uninstall", lambda: 0)
rc = run_uninstall(yes=True)
assert rc == 0
assert "opensre has been uninstalled" in capsys.readouterr().out
def test_run_uninstall_pip_failure_shows_hint(
monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]
) -> None:
monkeypatch.setattr("surfaces.cli.lifecycle.uninstall._data_dirs", lambda: [])
monkeypatch.setattr("surfaces.cli.lifecycle.uninstall._is_binary_install", lambda: False)
monkeypatch.setattr("surfaces.cli.lifecycle.uninstall._pip_uninstall", lambda: 1)
monkeypatch.setattr("surfaces.cli.lifecycle.uninstall._is_windows", lambda: False)
rc = run_uninstall(yes=True)
assert rc == 1
err = capsys.readouterr().err
assert "pip uninstall failed" in err
assert "retry manually" in err
def test_run_uninstall_pip_failure_windows_hint(
monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]
) -> None:
monkeypatch.setattr("surfaces.cli.lifecycle.uninstall._data_dirs", lambda: [])
monkeypatch.setattr("surfaces.cli.lifecycle.uninstall._is_binary_install", lambda: False)
monkeypatch.setattr("surfaces.cli.lifecycle.uninstall._pip_uninstall", lambda: 1)
monkeypatch.setattr("surfaces.cli.lifecycle.uninstall._is_windows", lambda: True)
rc = run_uninstall(yes=True)
assert rc == 1
assert "pip uninstall" in capsys.readouterr().err
def test_run_uninstall_binary_removes_executable(
monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str], tmp_path: Path
) -> None:
fake_exe = tmp_path / "opensre"
fake_exe.write_bytes(b"\x7fELF")
monkeypatch.setattr("surfaces.cli.lifecycle.uninstall._data_dirs", lambda: [])
monkeypatch.setattr("surfaces.cli.lifecycle.uninstall._is_binary_install", lambda: True)
monkeypatch.setattr("surfaces.cli.lifecycle.uninstall.sys.executable", str(fake_exe))
rc = run_uninstall(yes=True)
assert rc == 0
assert not fake_exe.exists()
assert "binary" in capsys.readouterr().out
def test_run_uninstall_onedir_binary_removes_launcher_and_app_dir(
monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str], tmp_path: Path
) -> None:
install_dir = tmp_path / "bin"
app_dir = install_dir / ".opensre-app"
internal = app_dir / "_internal"
internal.mkdir(parents=True)
fake_exe = app_dir / "opensre"
fake_exe.write_bytes(b"\x7fELF")
launcher = install_dir / "opensre"
launcher.symlink_to(fake_exe)
monkeypatch.setattr("surfaces.cli.lifecycle.uninstall._data_dirs", lambda: [])
monkeypatch.setattr("surfaces.cli.lifecycle.uninstall._is_binary_install", lambda: True)
monkeypatch.setattr("surfaces.cli.lifecycle.uninstall.sys.executable", str(fake_exe))
monkeypatch.setattr("shutil.which", lambda _name: str(launcher))
rc = run_uninstall(yes=True)
assert rc == 0
assert not launcher.exists()
assert not launcher.is_symlink()
assert not app_dir.exists()
out = capsys.readouterr().out
assert str(launcher) in out
assert str(app_dir) in out
def test_run_uninstall_dir_removal_error_sets_exit_1(
monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str], tmp_path: Path
) -> None:
d = tmp_path / "locked_dir"
d.mkdir()
monkeypatch.setattr("surfaces.cli.lifecycle.uninstall._data_dirs", lambda: [d])
monkeypatch.setattr("surfaces.cli.lifecycle.uninstall._is_binary_install", lambda: False)
monkeypatch.setattr("surfaces.cli.lifecycle.uninstall._pip_uninstall", lambda: 0)
def _fail(path: str) -> None:
raise OSError("Permission denied")
monkeypatch.setattr("shutil.rmtree", _fail)
rc = run_uninstall(yes=True)
assert rc == 1
assert "errors" in capsys.readouterr().err
def test_uninstall_command_registered() -> None:
runner = CliRunner()
result = runner.invoke(cli, ["uninstall", "--help"])
assert result.exit_code == 0
assert "uninstall" in result.output.lower()
def test_uninstall_command_yes_flag_skips_prompt() -> None:
runner = CliRunner()
with (
patch("surfaces.cli.lifecycle.uninstall._data_dirs", return_value=[]),
patch("surfaces.cli.lifecycle.uninstall._is_binary_install", return_value=False),
patch("surfaces.cli.lifecycle.uninstall._pip_uninstall", return_value=0),
):
result = runner.invoke(cli, ["uninstall", "--yes"])
assert result.exit_code == 0
assert "opensre has been uninstalled" in result.output
def test_uninstall_command_short_yes_flag() -> None:
runner = CliRunner()
with (
patch("surfaces.cli.lifecycle.uninstall._data_dirs", return_value=[]),
patch("surfaces.cli.lifecycle.uninstall._is_binary_install", return_value=False),
patch("surfaces.cli.lifecycle.uninstall._pip_uninstall", return_value=0),
):
result = runner.invoke(cli, ["uninstall", "-y"])
assert result.exit_code == 0
def test_data_dirs_includes_config_opensre_path() -> None:
from surfaces.cli.lifecycle.uninstall import _data_dirs
paths = _data_dirs()
path_strs = [str(p) for p in paths]
assert any(".opensre" in s for s in path_strs), "main ~/.opensre path missing"
assert any(".config" in s and "opensre" in s for s in path_strs), (
"~/.config/opensre cleanup path missing"
)
def test_uninstall_help_describes_command() -> None:
runner = CliRunner()
result = runner.invoke(cli, ["uninstall", "--help"])
assert result.exit_code == 0
assert "Remove opensre and all local data from this machine." in result.output