4b6817381b
Benchmark image — build + push to ECR (any adapter) / build + push (push) Waiting to run
CI / quality (ubuntu-latest) (push) Waiting to run
CI / test (tools-runtime) (push) Waiting to run
CI / test (e2e-general) (push) Waiting to run
CI / test (cli-runtime) (push) Waiting to run
CI / test (e2e-provider-and-openclaw) (push) Waiting to run
CI / test (integrations-and-misc) (push) Waiting to run
CI / coverage-report (push) Blocked by required conditions
CI / test-kubernetes (push) Waiting to run
CI / should-run-thorough (push) Waiting to run
CI / test-thorough (cloudwatch-demo) (push) Blocked by required conditions
CI / test-thorough (flink-ecs) (push) Blocked by required conditions
CI / test-thorough (upstream-lambda) (push) Blocked by required conditions
CI / test-thorough (prefect-ecs-fargate) (push) Blocked by required conditions
CodeQL / Analyze (python) (push) Waiting to run
Release / build-binaries (zip, opensre.exe, onefile, windows-latest, windows-x64) (push) Blocked by required conditions
Release / publish-release (push) Blocked by required conditions
Release / publish-main-release (push) Blocked by required conditions
Release / prepare (push) Waiting to run
Release / verify (push) Blocked by required conditions
Release / build-python-dist (push) Blocked by required conditions
Release / build-binaries (tar.gz, opensre, onedir, macos-15-intel, darwin-x64) (push) Blocked by required conditions
Release / build-binaries (tar.gz, opensre, onedir, macos-latest, darwin-arm64) (push) Blocked by required conditions
Release / build-binaries (tar.gz, opensre, onedir, ubuntu-22.04, linux-x64) (push) Blocked by required conditions
Release / build-binaries (tar.gz, opensre, onedir, ubuntu-22.04-arm, linux-arm64) (push) Blocked by required conditions
Synthetic Deterministic Tests / Synthetic offline (deterministic) (push) Waiting to run
Interactive Shell Live (PR + post-merge) / turn-checks (no-LLM) (push) Waiting to run
Interactive Shell Live (PR + post-merge) / turn-live shard ${{ matrix.shard_index }} (push) Waiting to run
CI (OpenClaw E2E) / openclaw test (push) Has been cancelled
293 lines
9.6 KiB
Python
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
|