fix(tui): repair shell-safety test scaffolding
CI / lint (pull_request) Successful in 49s
CI / quality (pull_request) Successful in 48s
CI / typecheck (pull_request) Successful in 1m17s
CI / security (pull_request) Successful in 1m30s
CI / build (pull_request) Successful in 33s
CI / helm (pull_request) Successful in 29s
CI / push-validation (pull_request) Successful in 22s
CI / unit_tests (pull_request) Successful in 6m26s
CI / docker (pull_request) Successful in 1m48s
CI / coverage (pull_request) Failing after 13m18s
CI / integration_tests (pull_request) Successful in 21m6s
CI / status-check (pull_request) Failing after 3s

Three independent test-scaffold defects blocked the unit_tests and
integration_tests gates on PR #6361's shell-safety wiring:

- features/steps/_tui_helpers.py: the mocked-shell helper patched
  cleveragents.tui.input.shell_exec.run_shell_command, but modes.py
  binds the symbol into its own namespace via `from ... import`. The
  patch was inert, and only the CLEVERAGENTS_ALLOW_DANGEROUS_SHELL=1
  gate kept the real `rm -rf /tmp` from running in the behave runner.
  Patch the use site (modes.run_shell_command) instead.

- src/cleveragents/tui/widgets/prompt.py: _FallbackPromptInput (used
  whenever Textual is mocked or unavailable) had no add_class /
  remove_class / has_class, so the new "prompt should be marked as
  dangerous" assertions raised AttributeError and the three new
  scenarios errored. The production path (_TextualPromptInput) already
  inherits these from textual.containers.Horizontal; the fallback now
  mirrors that contract via a small self._classes set.

- robot/tui_shell_safety.robot: Catenate's space-based argument
  separator collapses multi-space indentation, so the Python function
  bodies (warn_callback, deny) landed at column 0 and the helper
  scripts died with IndentationError before either assertion ran.
  Preserve the 4-space indent with ${SPACE * 4} markers.

ISSUES CLOSED: #6361
This commit is contained in:
2026-05-31 15:09:20 -04:00
committed by drew
parent 16c89e8ad9
commit 75bf285864
3 changed files with 32 additions and 7 deletions
+10 -2
View File
@@ -19,13 +19,21 @@ def _submit_text(context, text: str) -> None:
def _submit_text_with_mocked_shell(context, text: str, stdout: str = "mocked") -> None:
"""Submit *text* while faking shell execution."""
"""Submit *text* while faking shell execution.
The patch target is the use site in ``modes`` rather than the
definition site in ``shell_exec``: ``modes`` did
``from cleveragents.tui.input.shell_exec import run_shell_command``,
binding the symbol into its own namespace; patching the source
module would leave that binding (and therefore the real
``subprocess.run`` call) untouched.
"""
def fake_run(command: str, **_: Any) -> ShellResult:
return ShellResult(command=command, exit_code=0, stdout=stdout, stderr="")
with patch(
"cleveragents.tui.input.shell_exec.run_shell_command",
"cleveragents.tui.input.modes.run_shell_command",
side_effect=fake_run,
):
_submit_text(context, text)
+4 -4
View File
@@ -20,8 +20,8 @@ Shell Safety Service Blocks Denied Command
... warnings: list[DangerousCommandWarning] = []
...
... def warn_callback(warning: DangerousCommandWarning) -> bool:
... warnings.append(warning)
... return False
... ${SPACE * 4}warnings.append(warning)
... ${SPACE * 4}return False
...
... router = InputModeRouter(lambda cmd: "handled", shell_safety=ShellSafetyService(warn_callback=warn_callback))
... os.environ.pop("CLEVERAGENTS_ALLOW_DANGEROUS_SHELL", None)
@@ -47,8 +47,8 @@ Shell Confirm Callback Gates All Commands
... counter = {"count": 0}
...
... def deny(command: str) -> bool:
... counter["count"] += 1
... return False
... ${SPACE * 4}counter["count"] += 1
... ${SPACE * 4}return False
...
... result = run_shell_command("chmod -R 777 /tmp/test-shell-safety", confirm_dangerous=deny)
... assert counter["count"] == 1, f"Expected confirm to be invoked once, got {counter['count']}"
+18 -1
View File
@@ -248,12 +248,20 @@ class _TextualPromptInput(_PromptSymbolMixin, _HorizontalBase):
class _FallbackPromptInput(_PromptSymbolMixin):
"""Fallback prompt input used when Textual is unavailable."""
"""Fallback prompt input used when Textual is unavailable.
Mirrors the Textual Widget CSS-class API (``add_class`` /
``remove_class`` / ``has_class``) so callers that toggle styling
classes on the outer prompt (e.g. ``add_class("dangerous")`` from
the shell-safety surfacing path) work uniformly across production
and fallback/mock paths.
"""
def __init__(self, placeholder: str = "", **_: object) -> None:
self.placeholder = placeholder
self._input = cast(_MutableValueInput, _InputBase())
self._current_symbol = _PROMPT_SYMBOLS[InputMode.NORMAL]
self._classes: set[str] = set()
self._update_symbol(self._input.value)
@property
@@ -270,6 +278,15 @@ class _FallbackPromptInput(_PromptSymbolMixin):
if callable(focus):
focus()
def add_class(self, name: str) -> None:
self._classes.add(name)
def remove_class(self, name: str) -> None:
self._classes.discard(name)
def has_class(self, name: str) -> bool:
return name in self._classes
def _apply_symbol(self, symbol: str) -> None:
self._current_symbol = symbol