mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-07-31 19:16:29 +00:00
Systematic prune per AGENTS.md test policy, one pass over every major test tree (gateway, hermes_cli, tools, agent, run_agent, plugins, cli, cron, tui_gateway, honcho/openviking, root-level): - DELETE: source-reading tests (read_text/getsource on prod files), change-detector tests (exact catalog counts, model-name snapshots, config version literals), mock-echo tests (assert a mock returns what it was told), assertion-free/trivial tests, near-duplicate parametrizations (boundaries + one representative kept), async/sync twin duplicates, cosmetic within-file variations. - KEEP (mandatory): security/redaction/approval guards, message-role alternation invariants, prompt-caching/deterministic-call-id invariants, issue-number regression tests (deduped), E2E tests. - 6 test files deleted outright (script-style/no-assert or fully redundant); conftest.py, fakes/, fixtures/ untouched. - tests/acp/conftest.py added: autouse fixture stubs the live models.dev/GitHub/Copilot/Anthropic inventory fetches that ACP server tests performed on every session create — test_server.py 147s → 3.4s, and the tests are now genuinely hermetic. - Sleep-based slowness shrunk where safe (codex_ttfb_watchdog, compression_concurrent_fork, etc.); no wall-clock assertion tightened. Verification: full hermetic suite via scripts/run_tests.sh — 2439 files, 31,130 tests passed, 0 failed, 0 flaky retries, 315s wall (baseline: 583s wall, 13,564s subprocess CPU).
193 lines
5.7 KiB
Python
193 lines
5.7 KiB
Python
"""Tests for cli.HermesCLI._confirm_destructive_slash.
|
|
|
|
Drives the helper directly via __get__ on a SimpleNamespace stand-in so we
|
|
don't have to construct a full HermesCLI (which requires extensive setup).
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import queue
|
|
from types import SimpleNamespace
|
|
from unittest.mock import patch
|
|
|
|
|
|
def _bound(fn, instance):
|
|
"""Bind an unbound method to a stand-in instance."""
|
|
return fn.__get__(instance, type(instance))
|
|
|
|
|
|
def _make_self(prompt_response):
|
|
"""Build a minimal stand-in 'self' for _confirm_destructive_slash."""
|
|
from cli import HermesCLI
|
|
|
|
self_ = SimpleNamespace(
|
|
_app=None,
|
|
_prompt_text_input=lambda _prompt: prompt_response,
|
|
_prompt_text_input_modal=lambda **_kw: prompt_response,
|
|
)
|
|
self_._normalize_slash_confirm_choice = _bound(
|
|
HermesCLI._normalize_slash_confirm_choice, self_,
|
|
)
|
|
return self_
|
|
|
|
|
|
|
|
|
|
def test_gate_on_choice_once_returns_once():
|
|
"""When the gate is on and the user picks '1', return 'once'."""
|
|
from cli import HermesCLI
|
|
|
|
self_ = _make_self(prompt_response="1")
|
|
|
|
with patch(
|
|
"cli.load_cli_config",
|
|
return_value={"approvals": {"destructive_slash_confirm": True}},
|
|
):
|
|
result = _bound(HermesCLI._confirm_destructive_slash, self_)(
|
|
"clear", "detail",
|
|
)
|
|
|
|
assert result == "once"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_gate_on_choice_always_persists_and_returns_always():
|
|
"""User picks 'always' → returns 'always' AND
|
|
save_config_value('approvals.destructive_slash_confirm', False) was called."""
|
|
from cli import HermesCLI
|
|
|
|
self_ = _make_self(prompt_response="2")
|
|
|
|
saves = []
|
|
def _fake_save(key, value):
|
|
saves.append((key, value))
|
|
return True
|
|
|
|
with patch(
|
|
"cli.load_cli_config",
|
|
return_value={"approvals": {"destructive_slash_confirm": True}},
|
|
), patch("cli.save_config_value", _fake_save):
|
|
result = _bound(HermesCLI._confirm_destructive_slash, self_)(
|
|
"clear", "detail",
|
|
)
|
|
|
|
assert result == "always"
|
|
assert ("approvals.destructive_slash_confirm", False) in saves
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Inline-skip escape hatch (issue #30768)
|
|
#
|
|
# Users on platforms where the prompt_toolkit modal doesn't dispatch keys
|
|
# (currently native Windows PowerShell) need a way to bypass the confirmation
|
|
# without flipping the config gate. ``/reset now``, ``/new --yes``, ``/clear
|
|
# -y`` all skip the modal and return "once" immediately.
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
def test_split_destructive_skip_recognized_tokens():
|
|
"""``now``, ``--yes``, and ``-y`` are recognized as skip tokens."""
|
|
from cli import HermesCLI
|
|
|
|
assert HermesCLI._split_destructive_skip("/reset now") == ("", True)
|
|
assert HermesCLI._split_destructive_skip("/clear --yes") == ("", True)
|
|
assert HermesCLI._split_destructive_skip("/undo -y") == ("", True)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_split_destructive_skip_handles_empty_and_none():
|
|
"""Defensive against missing/empty input."""
|
|
from cli import HermesCLI
|
|
|
|
assert HermesCLI._split_destructive_skip(None) == ("", False)
|
|
assert HermesCLI._split_destructive_skip("") == ("", False)
|
|
assert HermesCLI._split_destructive_skip(" ") == ("", False)
|
|
|
|
|
|
def test_confirm_destructive_slash_now_skips_modal():
|
|
"""``/reset now`` skips the modal even when the gate is on."""
|
|
from cli import HermesCLI
|
|
|
|
# Build a prompt stub that fails the test if invoked — proving the modal
|
|
# was never reached.
|
|
def _explode(**_kw):
|
|
raise AssertionError("modal must not be invoked when inline-skip present")
|
|
|
|
self_ = SimpleNamespace(
|
|
_app=None,
|
|
_prompt_text_input_modal=_explode,
|
|
)
|
|
self_._normalize_slash_confirm_choice = _bound(
|
|
HermesCLI._normalize_slash_confirm_choice, self_,
|
|
)
|
|
self_._split_destructive_skip = HermesCLI._split_destructive_skip # classmethod
|
|
|
|
with patch(
|
|
"cli.load_cli_config",
|
|
return_value={"approvals": {"destructive_slash_confirm": True}},
|
|
):
|
|
result = _bound(HermesCLI._confirm_destructive_slash, self_)(
|
|
"new", "detail", cmd_original="/reset now",
|
|
)
|
|
|
|
assert result == "once"
|
|
|
|
|
|
def test_confirm_destructive_slash_yes_flag_skips_modal():
|
|
"""``--yes`` flag is equivalent to ``now``."""
|
|
from cli import HermesCLI
|
|
|
|
def _explode(**_kw):
|
|
raise AssertionError("modal must not be invoked when --yes present")
|
|
|
|
self_ = SimpleNamespace(
|
|
_app=None,
|
|
_prompt_text_input_modal=_explode,
|
|
)
|
|
self_._normalize_slash_confirm_choice = _bound(
|
|
HermesCLI._normalize_slash_confirm_choice, self_,
|
|
)
|
|
self_._split_destructive_skip = HermesCLI._split_destructive_skip
|
|
|
|
with patch(
|
|
"cli.load_cli_config",
|
|
return_value={"approvals": {"destructive_slash_confirm": True}},
|
|
):
|
|
result = _bound(HermesCLI._confirm_destructive_slash, self_)(
|
|
"new", "detail", cmd_original="/new --yes My Session",
|
|
)
|
|
|
|
assert result == "once"
|
|
|
|
|
|
def test_confirm_destructive_slash_no_skip_token_still_prompts():
|
|
"""Without a skip token the gate-on path still consults the modal."""
|
|
from cli import HermesCLI
|
|
|
|
self_ = _make_self(prompt_response="3") # cancel
|
|
self_._split_destructive_skip = HermesCLI._split_destructive_skip
|
|
|
|
with patch(
|
|
"cli.load_cli_config",
|
|
return_value={"approvals": {"destructive_slash_confirm": True}},
|
|
):
|
|
result = _bound(HermesCLI._confirm_destructive_slash, self_)(
|
|
"new", "detail", cmd_original="/new My Session",
|
|
)
|
|
|
|
# Prompt was reached and returned cancel → None.
|
|
assert result is None
|