"""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)