test(tui): fix stale mocks + xdist flakes in TUI test suite

All 61 TUI-related tests green across 3 consecutive xdist runs.

tests/tui_gateway/test_protocol.py:
- rename `get_messages` → `get_messages_as_conversation` on mock DB (method
  was renamed in the real backend, test was still stubbing the old name)
- update tool-message shape expectation: `{role, name, context}` matches
  current `_history_to_messages` output, not the legacy `{role, text}`

tests/hermes_cli/test_tui_resume_flow.py:
- `cmd_chat` grew a first-run provider-gate that bailed to "Run: hermes
  setup" before `_launch_tui` was ever reached; 3 tests stubbed
  `_resolve_last_session` + `_launch_tui` but not the gate
- factored a `main_mod` fixture that stubs `_has_any_provider_configured`,
  reused by all three tests

tests/test_tui_gateway_server.py:
- `test_config_set_personality_resets_history_and_returns_info` was flaky
  under xdist because the real `_write_config_key` touches
  `~/.hermes/config.yaml`, racing with any other worker that writes
  config. Stub it in the test.
This commit is contained in:
Brooklyn Nicholson 2026-04-16 19:07:49 -05:00
commit 7f1204840d
36 changed files with 4514 additions and 52 deletions

View file

@ -141,3 +141,116 @@ class TestCliApprovalUi:
assert "archive-" in rendered
assert "keyring.gpg" in rendered
assert "status=progress" in rendered
def test_approval_display_preserves_command_and_choices_with_long_description(self):
"""Regression: long tirith descriptions used to push approve/deny off-screen.
The panel must always render the command and every choice, even when
the description would otherwise wrap into 10+ lines. The description
gets truncated with a marker instead.
"""
cli = _make_cli_stub()
long_desc = (
"Security scan — [CRITICAL] Destructive shell command with wildcard expansion: "
"The command performs a recursive deletion of log files which may contain "
"audit information relevant to active incident investigations, running services "
"that rely on log files for state, rotated archives, and other system artifacts. "
"Review whether this is intended before approving. Consider whether a targeted "
"deletion with more specific filters would better match the intent."
)
cli._approval_state = {
"command": "rm -rf /var/log/apache2/*.log",
"description": long_desc,
"choices": ["once", "session", "always", "deny"],
"selected": 0,
"response_queue": queue.Queue(),
}
# Simulate a compact terminal where the old unbounded panel would overflow.
import shutil as _shutil
with patch("cli.shutil.get_terminal_size",
return_value=_shutil.os.terminal_size((100, 20))):
fragments = cli._get_approval_display_fragments()
rendered = "".join(text for _style, text in fragments)
# Command must be fully visible (rm -rf /var/log/apache2/*.log is short).
assert "rm -rf /var/log/apache2/*.log" in rendered
# Every choice must render — this is the core bug: approve/deny were
# getting clipped off the bottom of the panel.
assert "Allow once" in rendered
assert "Allow for this session" in rendered
assert "Add to permanent allowlist" in rendered
assert "Deny" in rendered
# The bottom border must render (i.e. the panel is self-contained).
assert rendered.rstrip().endswith("")
# The description gets truncated — marker should appear.
assert "(description truncated)" in rendered
def test_approval_display_skips_description_on_very_short_terminal(self):
"""On a 12-row terminal, only the command and choices have room.
The description is dropped entirely rather than partially shown, so the
choices never get clipped.
"""
cli = _make_cli_stub()
cli._approval_state = {
"command": "rm -rf /var/log/apache2/*.log",
"description": "recursive delete",
"choices": ["once", "session", "always", "deny"],
"selected": 0,
"response_queue": queue.Queue(),
}
import shutil as _shutil
with patch("cli.shutil.get_terminal_size",
return_value=_shutil.os.terminal_size((100, 12))):
fragments = cli._get_approval_display_fragments()
rendered = "".join(text for _style, text in fragments)
# Command visible.
assert "rm -rf /var/log/apache2/*.log" in rendered
# All four choices visible.
for label in ("Allow once", "Allow for this session",
"Add to permanent allowlist", "Deny"):
assert label in rendered, f"choice {label!r} missing"
def test_approval_display_truncates_giant_command_in_view_mode(self):
"""If the user hits /view on a massive command, choices still render.
The command gets truncated with a marker; the description gets dropped
if there's no remaining row budget.
"""
cli = _make_cli_stub()
# 50 lines of command when wrapped at ~64 chars.
giant_cmd = "bash -c 'echo " + ("x" * 3000) + "'"
cli._approval_state = {
"command": giant_cmd,
"description": "shell command via -c/-lc flag",
"choices": ["once", "session", "always", "deny"],
"selected": 0,
"show_full": True,
"response_queue": queue.Queue(),
}
import shutil as _shutil
with patch("cli.shutil.get_terminal_size",
return_value=_shutil.os.terminal_size((100, 24))):
fragments = cli._get_approval_display_fragments()
rendered = "".join(text for _style, text in fragments)
# All four choices visible even with a huge command.
for label in ("Allow once", "Allow for this session",
"Add to permanent allowlist", "Deny"):
assert label in rendered, f"choice {label!r} missing"
# Command got truncated with a marker.
assert "(command truncated" in rendered