mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-07-30 19:09:28 +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).
278 lines
8.1 KiB
Python
278 lines
8.1 KiB
Python
"""Tests for agent/display.py — build_tool_preview() and inline diff previews."""
|
|
|
|
import json
|
|
import pytest
|
|
from unittest.mock import MagicMock
|
|
|
|
import agent.display as display_module
|
|
from agent.display import (
|
|
build_tool_preview,
|
|
capture_local_edit_snapshot,
|
|
extract_edit_diff,
|
|
get_cute_tool_message,
|
|
redact_tool_args_for_display,
|
|
set_tool_preview_max_len,
|
|
_render_inline_unified_diff,
|
|
_summarize_rendered_diff_sections,
|
|
render_edit_diff_with_delta,
|
|
)
|
|
|
|
|
|
@pytest.fixture(autouse=True)
|
|
def reset_tool_preview_max_len():
|
|
set_tool_preview_max_len(0)
|
|
yield
|
|
set_tool_preview_max_len(0)
|
|
|
|
|
|
def test_cute_tool_message_falls_back_when_renderer_raises(monkeypatch):
|
|
def _boom(*_args, **_kwargs):
|
|
raise RuntimeError("cosmetic failure")
|
|
|
|
monkeypatch.setattr(display_module, "_get_cute_tool_message", _boom)
|
|
|
|
assert get_cute_tool_message("web_extract", {"urls": []}, 0.25) == (
|
|
"┊ ⚡ web_extra completed 0.2s"
|
|
)
|
|
|
|
|
|
class TestBuildToolPreview:
|
|
"""Tests for build_tool_preview defensive handling and normal operation."""
|
|
|
|
def test_none_args_returns_none(self):
|
|
"""PR #453: None args should not crash, should return None."""
|
|
assert build_tool_preview("terminal", None) is None
|
|
|
|
def test_empty_dict_returns_none(self):
|
|
"""Empty dict has no keys to preview."""
|
|
assert build_tool_preview("terminal", {}) is None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_browser_type_preview_redacts_api_key(self):
|
|
secret = "sk-proj-ABCD1234567890EFGH"
|
|
result = build_tool_preview("browser_type", {"ref": "@e3", "text": secret})
|
|
assert result is not None
|
|
assert secret not in result
|
|
assert "sk-pro" in result and "..." in result
|
|
|
|
def test_browser_type_preview_keeps_normal_text(self):
|
|
text = "hello world search query"
|
|
result = build_tool_preview("browser_type", {"ref": "@e3", "text": text})
|
|
assert result is not None
|
|
assert text in result
|
|
|
|
def test_browser_type_display_args_redact_api_key(self):
|
|
secret = "ghp_ABCDEFGHIJ1234567890"
|
|
safe_args = redact_tool_args_for_display(
|
|
"browser_type", {"ref": "@e3", "text": secret}
|
|
)
|
|
assert secret not in str(safe_args)
|
|
assert safe_args["ref"] == "@e3"
|
|
assert safe_args["text"].startswith("ghp_AB")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_delegate_task_batch_preview_respects_max_len(self):
|
|
result = build_tool_preview(
|
|
"delegate_task",
|
|
{"tasks": [{"goal": "A" * 80}, {"goal": "B" * 80}]},
|
|
max_len=30,
|
|
)
|
|
assert result == "2 tasks: AAAAAAAAAAAAAAAAAA..."
|
|
assert len(result) == 30
|
|
|
|
def test_false_like_args_zero(self):
|
|
"""Non-dict falsy values should return None, not crash."""
|
|
assert build_tool_preview("terminal", 0) is None
|
|
assert build_tool_preview("terminal", "") is None
|
|
assert build_tool_preview("terminal", []) is None
|
|
|
|
|
|
class TestCuteToolMessagePreviewLength:
|
|
|
|
|
|
def test_search_files_preview_uses_positive_configured_limit_not_default(self):
|
|
set_tool_preview_max_len(80)
|
|
pattern = "function.formatToolCall.context.preview.compactPreview.maxLength.truncate"
|
|
|
|
line = get_cute_tool_message("search_files", {"pattern": pattern}, 0.1)
|
|
|
|
assert pattern in line
|
|
assert "..." not in line
|
|
|
|
|
|
|
|
|
|
|
|
def test_browser_type_cute_message_redacts_api_key(self):
|
|
secret = "sk-proj-ABCD1234567890EFGH"
|
|
line = get_cute_tool_message(
|
|
"browser_type",
|
|
{"ref": "@password", "text": secret},
|
|
0.1,
|
|
result='{"success": true, "typed": "sk-pro...EFGH"}',
|
|
)
|
|
|
|
assert secret not in line
|
|
assert "sk-pro" in line
|
|
|
|
def test_browser_type_cute_message_keeps_normal_text(self):
|
|
text = "hello world"
|
|
line = get_cute_tool_message(
|
|
"browser_type",
|
|
{"ref": "@search", "text": text},
|
|
0.1,
|
|
result='{"success": true, "typed": "hello world"}',
|
|
)
|
|
|
|
assert text in line
|
|
|
|
|
|
class TestEditDiffPreview:
|
|
|
|
|
|
|
|
def test_extract_edit_diff_uses_local_snapshot_for_write_file(self, tmp_path):
|
|
target = tmp_path / "note.txt"
|
|
target.write_text("old\n", encoding="utf-8")
|
|
|
|
snapshot = capture_local_edit_snapshot("write_file", {"path": str(target)})
|
|
|
|
target.write_text("new\n", encoding="utf-8")
|
|
|
|
diff = extract_edit_diff(
|
|
"write_file",
|
|
'{"bytes_written": 4}',
|
|
function_args={"path": str(target)},
|
|
snapshot=snapshot,
|
|
)
|
|
|
|
assert diff is not None
|
|
assert "--- a/" in diff
|
|
assert "+++ b/" in diff
|
|
assert "-old" in diff
|
|
assert "+new" in diff
|
|
|
|
|
|
|
|
def test_render_edit_diff_with_delta_handles_renderer_errors(self, monkeypatch):
|
|
printer = MagicMock()
|
|
|
|
monkeypatch.setattr("agent.display._summarize_rendered_diff_sections", MagicMock(side_effect=RuntimeError("boom")))
|
|
|
|
rendered = render_edit_diff_with_delta(
|
|
"patch",
|
|
'{"diff": "--- a/x\\n+++ b/x\\n"}',
|
|
print_fn=printer,
|
|
)
|
|
|
|
assert rendered is False
|
|
assert printer.call_count == 0
|
|
|
|
|
|
def test_summarize_rendered_diff_sections_limits_file_count(self):
|
|
diff = "".join(
|
|
f"--- a/file{i}.py\n+++ b/file{i}.py\n+line{i}\n"
|
|
for i in range(8)
|
|
)
|
|
|
|
rendered = _summarize_rendered_diff_sections(diff, max_files=3, max_lines=50)
|
|
|
|
assert any("a/file0.py" in line for line in rendered)
|
|
assert any("a/file1.py" in line for line in rendered)
|
|
assert any("a/file2.py" in line for line in rendered)
|
|
assert not any("a/file7.py" in line for line in rendered)
|
|
assert "additional file" in rendered[-1]
|
|
|
|
|
|
class TestBuildToolLabel:
|
|
"""Friendly human-phrased tool labels for built-in tools."""
|
|
|
|
@pytest.fixture(autouse=True)
|
|
def _enable_friendly(self):
|
|
from agent.display import set_friendly_tool_labels
|
|
set_friendly_tool_labels(True)
|
|
yield
|
|
set_friendly_tool_labels(True)
|
|
|
|
def test_web_search_uses_for_connector(self):
|
|
from agent.display import build_tool_label
|
|
label = build_tool_label("web_search", {"query": "weather in NYC"})
|
|
assert label == 'Searching the web for weather in NYC'
|
|
|
|
def test_web_extract_reads_url(self):
|
|
from agent.display import build_tool_label
|
|
label = build_tool_label("web_extract", {"urls": ["https://example.com/page"]})
|
|
assert label is not None
|
|
assert label.startswith("Reading ")
|
|
assert "example.com/page" in label
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_disabled_falls_back_to_preview(self):
|
|
from agent.display import (
|
|
build_tool_label,
|
|
build_tool_preview,
|
|
set_friendly_tool_labels,
|
|
)
|
|
set_friendly_tool_labels(False)
|
|
args = {"query": "weather in NYC"}
|
|
label = build_tool_label("web_search", args)
|
|
# With the feature off, must match the raw preview exactly
|
|
assert label == build_tool_preview("web_search", args)
|
|
assert "Searching the web" not in (label or "")
|
|
|
|
|
|
|
|
class TestBuildStatusPhrase:
|
|
"""build_status_phrase — live working-state text for Slack's status line."""
|
|
|
|
|
|
|
|
def test_verb_only_when_args_none(self):
|
|
# live_status: "verb" mode passes args=None to suppress previews.
|
|
from agent.display import build_status_phrase
|
|
assert build_status_phrase("terminal", None) == "is running…"
|
|
assert build_status_phrase("read_file", None) == "is reading…"
|
|
|
|
|
|
|
|
def test_caps_length_for_slack_status_line(self):
|
|
from agent.display import build_status_phrase
|
|
phrase = build_status_phrase(
|
|
"terminal", {"command": "x" * 300}, max_len=49
|
|
)
|
|
assert phrase is not None and len(phrase) <= 49
|
|
assert phrase.endswith("…")
|
|
|
|
|
|
def test_respects_friendly_labels_toggle(self):
|
|
from agent.display import build_status_phrase, set_friendly_tool_labels
|
|
set_friendly_tool_labels(False)
|
|
try:
|
|
assert build_status_phrase("terminal", {"command": "ls"}) is None
|
|
finally:
|
|
set_friendly_tool_labels(True)
|
|
|