* refactor: re-architect tests to mirror the codebase
* Update tests.yml
* fix: add missing tool_error imports after registry refactor
* fix(tests): replace patch.dict with monkeypatch to prevent env var leaks under xdist
patch.dict(os.environ) can leak TERMINAL_ENV across xdist workers,
causing test_code_execution tests to hit the Modal remote path.
* fix(tests): fix update_check and telegram xdist failures
- test_update_check: replace patch("hermes_cli.banner.os.getenv") with
monkeypatch.setenv("HERMES_HOME") — banner.py no longer imports os
directly, it uses get_hermes_home() from hermes_constants.
- test_telegram_conflict/approval_buttons: provide real exception classes
for telegram.error mock (NetworkError, TimedOut, BadRequest) so the
except clause in connect() doesn't fail with "catching classes that do
not inherit from BaseException" when xdist pollutes sys.modules.
* fix(tests): accept unavailable_models kwarg in _prompt_model_selection mock
101 lines
3.6 KiB
Python
101 lines
3.6 KiB
Python
import queue
|
|
import threading
|
|
import time
|
|
from types import SimpleNamespace
|
|
from unittest.mock import MagicMock
|
|
|
|
from cli import HermesCLI
|
|
|
|
|
|
def _make_cli_stub():
|
|
cli = HermesCLI.__new__(HermesCLI)
|
|
cli._approval_state = None
|
|
cli._approval_deadline = 0
|
|
cli._approval_lock = threading.Lock()
|
|
cli._invalidate = MagicMock()
|
|
cli._app = SimpleNamespace(invalidate=MagicMock())
|
|
return cli
|
|
|
|
|
|
class TestCliApprovalUi:
|
|
def test_approval_callback_includes_view_for_long_commands(self):
|
|
cli = _make_cli_stub()
|
|
command = "sudo dd if=/tmp/githubcli-keyring.gpg of=/usr/share/keyrings/githubcli-archive-keyring.gpg bs=4M status=progress"
|
|
result = {}
|
|
|
|
def _run_callback():
|
|
result["value"] = cli._approval_callback(command, "disk copy")
|
|
|
|
thread = threading.Thread(target=_run_callback, daemon=True)
|
|
thread.start()
|
|
|
|
deadline = time.time() + 2
|
|
while cli._approval_state is None and time.time() < deadline:
|
|
time.sleep(0.01)
|
|
|
|
assert cli._approval_state is not None
|
|
assert "view" in cli._approval_state["choices"]
|
|
|
|
cli._approval_state["response_queue"].put("deny")
|
|
thread.join(timeout=2)
|
|
assert result["value"] == "deny"
|
|
|
|
def test_handle_approval_selection_view_expands_in_place(self):
|
|
cli = _make_cli_stub()
|
|
cli._approval_state = {
|
|
"command": "sudo dd if=/tmp/in of=/usr/share/keyrings/githubcli-archive-keyring.gpg bs=4M status=progress",
|
|
"description": "disk copy",
|
|
"choices": ["once", "session", "always", "deny", "view"],
|
|
"selected": 4,
|
|
"response_queue": queue.Queue(),
|
|
}
|
|
|
|
cli._handle_approval_selection()
|
|
|
|
assert cli._approval_state is not None
|
|
assert cli._approval_state["show_full"] is True
|
|
assert "view" not in cli._approval_state["choices"]
|
|
assert cli._approval_state["selected"] == 3
|
|
assert cli._approval_state["response_queue"].empty()
|
|
|
|
def test_approval_display_places_title_inside_box_not_border(self):
|
|
cli = _make_cli_stub()
|
|
cli._approval_state = {
|
|
"command": "sudo dd if=/tmp/in of=/usr/share/keyrings/githubcli-archive-keyring.gpg bs=4M status=progress",
|
|
"description": "disk copy",
|
|
"choices": ["once", "session", "always", "deny", "view"],
|
|
"selected": 0,
|
|
"response_queue": queue.Queue(),
|
|
}
|
|
|
|
fragments = cli._get_approval_display_fragments()
|
|
rendered = "".join(text for _style, text in fragments)
|
|
lines = rendered.splitlines()
|
|
|
|
assert lines[0].startswith("╭")
|
|
assert "Dangerous Command" not in lines[0]
|
|
assert any("Dangerous Command" in line for line in lines[1:3])
|
|
assert "Show full command" in rendered
|
|
assert "githubcli-archive-keyring.gpg" not in rendered
|
|
|
|
def test_approval_display_shows_full_command_after_view(self):
|
|
cli = _make_cli_stub()
|
|
full_command = "sudo dd if=/tmp/in of=/usr/share/keyrings/githubcli-archive-keyring.gpg bs=4M status=progress"
|
|
cli._approval_state = {
|
|
"command": full_command,
|
|
"description": "disk copy",
|
|
"choices": ["once", "session", "always", "deny"],
|
|
"selected": 0,
|
|
"show_full": True,
|
|
"response_queue": queue.Queue(),
|
|
}
|
|
|
|
fragments = cli._get_approval_display_fragments()
|
|
rendered = "".join(text for _style, text in fragments)
|
|
|
|
assert "..." not in rendered
|
|
assert "githubcli-" in rendered
|
|
assert "archive-" in rendered
|
|
assert "keyring.gpg" in rendered
|
|
assert "status=progress" in rendered
|