hermes-agent/tests/tools/test_clipboard.py
Teknium 39975613b1
test: prune wave 2 + speed fixes — 28,106 → 19,757 test functions, suite wall 315s → 294s
Second, deeper pass over tools/gateway/hermes_cli plus first pass over
the trees wave 1 missed (acp, acp_adapter, skills, computer_use, docker,
dashboard, conformance, monitoring, secret_sources, hermes_state,
providers). Same rubric as wave 1 (AGENTS.md test policy); security,
alternation/caching invariants, issue-number regressions, and E2E kept.

Real test-quality fixes found and rooted out along the way:
- tests/tools/test_command_guards.py made real auxiliary-LLM HTTPS calls
  (DEFAULT_CONFIG smart-approval leaked in) — pinned approval
  mode=manual via autouse fixture: 17.4s → 0.4s.
- test_model_switch_custom_providers.py / test_user_providers_model_switch.py
  silently probed live provider catalogs (~2s/test) — stubbed
  cached_provider_model_ids/provider_model_ids/fetch_api_models.
- test_telegram_noise_filter.py: 15-platform copy-paste matrix over
  shared gateway.run logic → 3 representative platforms (55s → 3.9s).
- test_gateway_shutdown.py: stop()'s 5s interrupt-deadline loop spun on
  MagicMock agents — interrupt.side_effect now clears _running_agents
  (22s → 1.0s).
- test_gateway_inactivity_timeout.py poll-harness timings shrunk 3-5x
  (24s → 1.1s); test_mcp_stability.py backoff/SIGTERM-grace sleeps
  patched (15.4s → 2.5s); test_async_delegation.py negative-drain wait
  5s → 0.5s.
- test_telegram_init_deadline.py: loop-block margin restored to 1.0s
  with rationale comment — the watchdog-dump assertion needs the loop
  blocked well past deadline+grace under parallel load (flaked once in
  the 40-worker verification run at a 0.2s margin).

Verification: full hermetic suite via scripts/run_tests.sh —
2,438 files, 21,718 tests passed, 0 failed, 293.9s wall.
Suite totals vs original baseline: 46,820 → 19,757 test functions
(−57.8%), wall 583.5s → 293.9s (−50%), subprocess CPU 13,564s → 11,623s.
2026-07-29 13:39:40 -07:00

561 lines
24 KiB
Python

"""Tests for clipboard image paste — clipboard extraction, multimodal conversion,
and CLI integration.
Coverage:
hermes_cli/clipboard.py — platform-specific image extraction (macOS, WSL, Wayland, X11)
cli.py — _try_attach_clipboard_image, _build_multimodal_content,
image attachment state, queue tuple routing
"""
import base64
import os
import queue
import subprocess
import sys
from pathlib import Path
from unittest.mock import patch, MagicMock, mock_open
import pytest
from hermes_cli.clipboard import (
save_clipboard_image,
has_clipboard_image,
_is_wsl,
_linux_save,
_macos_pngpaste,
_macos_osascript,
_macos_has_image,
_xclip_save,
_xclip_has_image,
_wsl_save,
_wsl_has_image,
_wayland_save,
_wayland_has_image,
_windows_save,
_windows_has_image,
_convert_to_png,
)
from cli import _should_auto_attach_clipboard_image_on_paste
FAKE_PNG = b"\x89PNG\r\n\x1a\n" + b"\x00" * 100
FAKE_BMP = b"BM" + b"\x00" * 100
FAKE_JPEG = b"\xff\xd8\xff\xe0" + b"\x00" * 100
# ═════════════════════════════════════════════════════════════════════════
# Level 1: Clipboard module — platform dispatch + tool interactions
# ═════════════════════════════════════════════════════════════════════════
class TestSaveClipboardImage:
def test_creates_parent_dirs(self, tmp_path):
dest = tmp_path / "deep" / "nested" / "out.png"
with patch("hermes_cli.clipboard.sys") as mock_sys:
mock_sys.platform = "linux"
with patch("hermes_cli.clipboard._linux_save", return_value=False):
save_clipboard_image(dest)
assert dest.parent.exists()
# ── macOS ────────────────────────────────────────────────────────────────
class TestMacosPngpaste:
def test_success_writes_file(self, tmp_path):
dest = tmp_path / "out.png"
def fake_run(cmd, **kw):
dest.write_bytes(FAKE_PNG)
return MagicMock(returncode=0)
with patch("hermes_cli.clipboard.subprocess.run", side_effect=fake_run):
assert _macos_pngpaste(dest) is True
assert dest.stat().st_size == len(FAKE_PNG)
def test_empty_file_rejected(self, tmp_path):
dest = tmp_path / "out.png"
def fake_run(cmd, **kw):
dest.write_bytes(b"")
return MagicMock(returncode=0)
with patch("hermes_cli.clipboard.subprocess.run", side_effect=fake_run):
assert _macos_pngpaste(dest) is False
class TestMacosHasImage:
@pytest.mark.parametrize("stdout, expected", [
("«class PNGf», «class ut16»", True),
("«class ut16», «class utf8»", False),
])
def test_image_class_detection(self, stdout, expected):
with patch("hermes_cli.clipboard.subprocess.run") as mock_run:
mock_run.return_value = MagicMock(stdout=stdout, returncode=0)
assert _macos_has_image() is expected
class TestMacosOsascript:
def test_success_with_png(self, tmp_path):
dest = tmp_path / "out.png"
calls = []
def fake_run(cmd, **kw):
calls.append(cmd)
if len(calls) == 1:
return MagicMock(stdout="«class PNGf», «class ut16»", returncode=0)
dest.write_bytes(FAKE_PNG)
return MagicMock(stdout="", returncode=0)
with patch("hermes_cli.clipboard.subprocess.run", side_effect=fake_run):
assert _macos_osascript(dest) is True
assert dest.stat().st_size > 0
def test_extraction_returns_fail(self, tmp_path):
dest = tmp_path / "out.png"
calls = []
def fake_run(cmd, **kw):
calls.append(cmd)
if len(calls) == 1:
return MagicMock(stdout="«class PNGf»", returncode=0)
return MagicMock(stdout="fail", returncode=0)
with patch("hermes_cli.clipboard.subprocess.run", side_effect=fake_run):
assert _macos_osascript(dest) is False
# ── WSL detection ────────────────────────────────────────────────────────
class TestIsWsl:
def setup_method(self):
# _is_wsl is hermes_constants.is_wsl; reset the function's own module
# globals so this stays stable even if hermes_constants was imported
# through a different module object earlier in a large xdist run.
import hermes_constants
hermes_constants._wsl_detected = None
_is_wsl.__globals__["_wsl_detected"] = None
def teardown_method(self):
# Reset again after the test so we don't leak a cached value
# (True/False) into whichever test the xdist worker runs next.
import hermes_constants
hermes_constants._wsl_detected = None
_is_wsl.__globals__["_wsl_detected"] = None
@pytest.mark.parametrize("content, expected", [
("Linux version 5.15.0 (microsoft-standard-WSL2)", True),
# GHA hosted runners are Azure VMs whose real /proc/version often
# contains "microsoft", so the patched `open` must actually be reached
# (setup_method clears the cache that would short-circuit it).
("Linux version 6.14.0-37-generic (buildd@lcy02-amd64-049)", False),
])
def test_detection_from_proc_version(self, content, expected):
with patch.dict(_is_wsl.__globals__, {"open": mock_open(read_data=content)}):
assert _is_wsl() is expected
def test_result_is_cached(self):
content = "Linux version 5.15.0 (microsoft-standard-WSL2)"
opener = mock_open(read_data=content)
with patch.dict(_is_wsl.__globals__, {"open": opener}):
assert _is_wsl() is True
assert _is_wsl() is True
opener.assert_called_once() # only read once
# ── WSL (powershell.exe) ────────────────────────────────────────────────
class TestWslHasImage:
@pytest.mark.parametrize("stdout, expected", [
("True\n", True),
("False\n", False),
])
def test_clipboard_image_probe(self, stdout, expected):
with patch("hermes_cli.clipboard.subprocess.run") as mock_run:
mock_run.return_value = MagicMock(stdout=stdout, returncode=0)
assert _wsl_has_image() is expected
def test_falls_back_to_get_clipboard_image(self):
with patch("hermes_cli.clipboard.subprocess.run") as mock_run:
mock_run.side_effect = [
MagicMock(stdout="False\n", returncode=0),
MagicMock(stdout="True\n", returncode=0),
]
assert _wsl_has_image() is True
assert mock_run.call_count == 2
class TestWslSave:
def test_successful_extraction(self, tmp_path):
dest = tmp_path / "out.png"
b64_png = base64.b64encode(FAKE_PNG).decode()
with patch("hermes_cli.clipboard.subprocess.run") as mock_run:
mock_run.return_value = MagicMock(stdout=b64_png + "\n", returncode=0)
assert _wsl_save(dest) is True
assert dest.read_bytes() == FAKE_PNG
def test_invalid_base64(self, tmp_path):
dest = tmp_path / "out.png"
with patch("hermes_cli.clipboard.subprocess.run") as mock_run:
mock_run.return_value = MagicMock(stdout="not-valid-base64!!!", returncode=0)
assert _wsl_save(dest) is False
# ── Wayland (wl-paste) ──────────────────────────────────────────────────
class TestWaylandHasImage:
@pytest.mark.parametrize("types, expected", [
("image/png\ntext/plain\n", True),
("text/html\nimage/bmp\n", True), # non-PNG image types count too
("text/plain\ntext/html\n", False),
])
def test_type_list_detection(self, types, expected):
with patch("hermes_cli.clipboard.subprocess.run") as mock_run:
mock_run.return_value = MagicMock(stdout=types, returncode=0)
assert _wayland_has_image() is expected
class TestWaylandSave:
def test_png_extraction(self, tmp_path):
dest = tmp_path / "out.png"
def fake_run(cmd, **kw):
if "--list-types" in cmd:
return MagicMock(stdout="image/png\ntext/plain\n", returncode=0)
# Extract call — write fake data to stdout file
if "stdout" in kw and hasattr(kw["stdout"], "write"):
kw["stdout"].write(FAKE_PNG)
return MagicMock(returncode=0)
with patch("hermes_cli.clipboard.subprocess.run", side_effect=fake_run):
assert _wayland_save(dest) is True
assert dest.stat().st_size > 0
def test_prefers_png_over_bmp(self, tmp_path):
"""When both PNG and BMP are available, PNG should be preferred."""
dest = tmp_path / "out.png"
calls = []
def fake_run(cmd, **kw):
calls.append(cmd)
if "--list-types" in cmd:
return MagicMock(
stdout="image/bmp\nimage/png\ntext/plain\n", returncode=0
)
if "stdout" in kw and hasattr(kw["stdout"], "write"):
kw["stdout"].write(FAKE_PNG)
return MagicMock(returncode=0)
with patch("hermes_cli.clipboard.subprocess.run", side_effect=fake_run):
assert _wayland_save(dest) is True
# Verify PNG was requested, not BMP
extract_cmd = calls[1]
assert "image/png" in extract_cmd
# ── X11 (xclip) ─────────────────────────────────────────────────────────
class TestXclipHasImage:
@pytest.mark.parametrize("targets, expected", [
("image/png\ntext/plain\n", True),
("text/plain\n", False),
])
def test_targets_detection(self, targets, expected):
with patch("hermes_cli.clipboard.subprocess.run") as mock_run:
mock_run.return_value = MagicMock(stdout=targets, returncode=0)
assert _xclip_has_image() is expected
class TestXclipSave:
def test_image_extraction_success(self, tmp_path):
dest = tmp_path / "out.png"
def fake_run(cmd, **kw):
if "TARGETS" in cmd:
return MagicMock(stdout="image/png\ntext/plain\n", returncode=0)
if "stdout" in kw and hasattr(kw["stdout"], "write"):
kw["stdout"].write(FAKE_PNG)
return MagicMock(returncode=0)
with patch("hermes_cli.clipboard.subprocess.run", side_effect=fake_run):
assert _xclip_save(dest) is True
assert dest.stat().st_size > 0
def test_extraction_fails_cleans_up(self, tmp_path):
dest = tmp_path / "out.png"
def fake_run(cmd, **kw):
if "TARGETS" in cmd:
return MagicMock(stdout="image/png\n", returncode=0)
raise subprocess.SubprocessError("pipe broke")
with patch("hermes_cli.clipboard.subprocess.run", side_effect=fake_run):
assert _xclip_save(dest) is False
assert not dest.exists()
# ── Linux dispatch ──────────────────────────────────────────────────────
class TestLinuxSave:
"""Test that _linux_save dispatches correctly to WSL → Wayland → X11."""
def setup_method(self):
import hermes_cli.clipboard as cb
cb._wsl_detected = None
def test_wsl_tried_first(self, tmp_path):
dest = tmp_path / "out.png"
with patch("hermes_cli.clipboard._is_wsl", return_value=True):
with patch("hermes_cli.clipboard._wsl_save", return_value=True) as m:
assert _linux_save(dest) is True
m.assert_called_once_with(dest)
def test_wayland_fails_falls_through_to_xclip(self, tmp_path):
dest = tmp_path / "out.png"
with patch("hermes_cli.clipboard._is_wsl", return_value=False):
with patch.dict(os.environ, {"WAYLAND_DISPLAY": "wayland-0"}):
with patch("hermes_cli.clipboard._wayland_save", return_value=False):
with patch("hermes_cli.clipboard._xclip_save", return_value=True) as m:
assert _linux_save(dest) is True
m.assert_called_once_with(dest)
# ── Native Windows (PowerShell) ─────────────────────────────────────────
class TestWindowsHasImage:
def setup_method(self):
import hermes_cli.clipboard as cb
cb._ps_exe = False # reset cache
def test_clipboard_has_image(self):
with patch("hermes_cli.clipboard._get_ps_exe", return_value="powershell"):
with patch("hermes_cli.clipboard.subprocess.run") as mock_run:
mock_run.return_value = MagicMock(stdout="True\n", returncode=0)
assert _windows_has_image() is True
def test_falls_back_to_get_clipboard_image(self):
with patch("hermes_cli.clipboard._get_ps_exe", return_value="powershell"):
with patch("hermes_cli.clipboard.subprocess.run") as mock_run:
mock_run.side_effect = [
MagicMock(stdout="False\n", returncode=0),
MagicMock(stdout="True\n", returncode=0),
]
assert _windows_has_image() is True
assert mock_run.call_count == 2
class TestWindowsSave:
def setup_method(self):
import hermes_cli.clipboard as cb
cb._ps_exe = False # reset cache
def test_successful_extraction(self, tmp_path):
dest = tmp_path / "out.png"
b64_png = base64.b64encode(FAKE_PNG).decode()
with patch("hermes_cli.clipboard._get_ps_exe", return_value="powershell"):
with patch("hermes_cli.clipboard.subprocess.run") as mock_run:
mock_run.return_value = MagicMock(stdout=b64_png + "\n", returncode=0)
assert _windows_save(dest) is True
assert dest.read_bytes() == FAKE_PNG
def test_falls_back_to_filedrop_image(self, tmp_path):
dest = tmp_path / "out.png"
b64_png = base64.b64encode(FAKE_PNG).decode()
with patch("hermes_cli.clipboard._get_ps_exe", return_value="powershell"):
with patch("hermes_cli.clipboard.subprocess.run") as mock_run:
mock_run.side_effect = [
MagicMock(stdout="", returncode=1),
MagicMock(stdout="", returncode=1),
MagicMock(stdout=b64_png + "\n", returncode=0),
]
assert _windows_save(dest) is True
assert mock_run.call_count == 3
assert dest.read_bytes() == FAKE_PNG
# ── BMP conversion ──────────────────────────────────────────────────────
class TestConvertToPng:
def test_pillow_conversion(self, tmp_path):
dest = tmp_path / "img.png"
dest.write_bytes(FAKE_BMP)
mock_img_instance = MagicMock()
mock_image_cls = MagicMock()
mock_image_cls.open.return_value = mock_img_instance
# `from PIL import Image` fetches PIL.Image from the PIL module
mock_pil_module = MagicMock()
mock_pil_module.Image = mock_image_cls
with patch.dict(sys.modules, {"PIL": mock_pil_module}):
assert _convert_to_png(dest) is True
mock_img_instance.save.assert_called_once_with(dest, "PNG")
@pytest.mark.parametrize("failure", ["nonzero-exit", "timeout"])
def test_imagemagick_failure_preserves_original(self, tmp_path, failure):
"""When ImageMagick can't convert, the original file must not be lost."""
dest = tmp_path / "img.png"
dest.write_bytes(FAKE_BMP)
side_effect = (
(lambda cmd, **kw: MagicMock(returncode=1))
if failure == "nonzero-exit"
else subprocess.TimeoutExpired("convert", 5)
)
with patch.dict(sys.modules, {"PIL": None, "PIL.Image": None}):
with patch("hermes_cli.clipboard.subprocess.run", side_effect=side_effect):
_convert_to_png(dest)
# Original file must still exist with original content
assert dest.exists(), "Original file was lost after failed conversion"
assert dest.read_bytes() == FAKE_BMP
# ── has_clipboard_image dispatch ─────────────────────────────────────────
class TestHasClipboardImage:
def setup_method(self):
import hermes_cli.clipboard as cb
cb._wsl_detected = None
def test_macos_dispatch(self):
with patch("hermes_cli.clipboard.sys") as mock_sys:
mock_sys.platform = "darwin"
with patch("hermes_cli.clipboard._macos_has_image", return_value=True) as m:
assert has_clipboard_image() is True
m.assert_called_once()
def test_wsl_falls_through_to_wayland_when_windows_path_empty(self):
"""WSLg often bridges images to wl-paste even when powershell.exe check fails."""
with patch("hermes_cli.clipboard.sys") as mock_sys:
mock_sys.platform = "linux"
with patch("hermes_cli.clipboard._is_wsl", return_value=True):
with patch("hermes_cli.clipboard._wsl_has_image", return_value=False) as wsl:
with patch.dict(os.environ, {"WAYLAND_DISPLAY": "wayland-0"}):
with patch("hermes_cli.clipboard._wayland_has_image", return_value=True) as wl:
assert has_clipboard_image() is True
wsl.assert_called_once()
wl.assert_called_once()
# ═════════════════════════════════════════════════════════════════════════
# Level 2: _preprocess_images_with_vision — image → text via vision tool
# ═════════════════════════════════════════════════════════════════════════
class TestPreprocessImagesWithVision:
"""Test vision-based image pre-processing for the CLI."""
@pytest.fixture
def cli(self):
"""Minimal HermesCLI with mocked internals."""
with patch("cli.load_cli_config") as mock_cfg:
mock_cfg.return_value = {
"model": {"default": "test/model", "base_url": "http://x", "provider": "auto"},
"terminal": {"timeout": 60},
"browser": {},
"compression": {"enabled": True},
"agent": {"max_turns": 10},
"display": {"compact": True},
"clarify": {},
"code_execution": {},
"delegation": {},
}
with patch.dict("os.environ", {"OPENROUTER_API_KEY": "test-key"}):
with patch("cli.CLI_CONFIG", mock_cfg.return_value):
from cli import HermesCLI
cli_obj = HermesCLI.__new__(HermesCLI)
# Manually init just enough state
cli_obj._attached_images = []
cli_obj._image_counter = 0
return cli_obj
def _make_image(self, tmp_path, name="test.png", content=FAKE_PNG):
img = tmp_path / name
img.write_bytes(content)
return img
def _mock_vision_success(self, description="A test image with colored pixels."):
"""Return an async mock that simulates a successful vision_analyze_tool call."""
import json
async def _fake_vision(**kwargs):
return json.dumps({"success": True, "analysis": description})
return _fake_vision
def test_single_image_with_text(self, cli, tmp_path):
img = self._make_image(tmp_path)
with patch("tools.vision_tools.vision_analyze_tool", side_effect=self._mock_vision_success()):
result = cli._preprocess_images_with_vision("Describe this", [img])
assert isinstance(result, str)
assert "A test image with colored pixels." in result
assert "Describe this" in result
assert str(img) in result
assert "base64," not in result # no raw base64 image content
def test_vision_exception_includes_path(self, cli, tmp_path):
img = self._make_image(tmp_path)
async def _explode(**kwargs):
raise RuntimeError("API down")
with patch("tools.vision_tools.vision_analyze_tool", side_effect=_explode):
result = cli._preprocess_images_with_vision("check this", [img])
assert isinstance(result, str)
assert str(img) in result # path still included for retry
# ═════════════════════════════════════════════════════════════════════════
# Level 3: _try_attach_clipboard_image — state management
# ═════════════════════════════════════════════════════════════════════════
class TestTryAttachClipboardImage:
"""Test the clipboard → state flow."""
@pytest.fixture
def cli(self):
from cli import HermesCLI
cli_obj = HermesCLI.__new__(HermesCLI)
cli_obj._attached_images = []
cli_obj._image_counter = 0
return cli_obj
def test_image_found_attaches(self, cli):
with patch("hermes_cli.clipboard.save_clipboard_image", return_value=True):
result = cli._try_attach_clipboard_image()
assert result is True
assert len(cli._attached_images) == 1
assert cli._image_counter == 1
def test_image_path_follows_naming_convention(self, cli):
with patch("hermes_cli.clipboard.save_clipboard_image", return_value=True):
cli._try_attach_clipboard_image()
path = cli._attached_images[0]
assert path.parent == Path(os.environ["HERMES_HOME"]) / "images"
assert path.name.startswith("clip_")
assert path.suffix == ".png"
class TestAutoAttachClipboardImageOnPaste:
@pytest.mark.parametrize("pasted, expected", [
(" hello world ", False), # real text paste — don't hijack it
(" \n\t ", True), # whitespace-only paste may be an image
])
def test_auto_attach_decision(self, pasted, expected):
assert _should_auto_attach_clipboard_image_on_paste(pasted) is expected
class TestVoiceSubmission:
@pytest.fixture
def cli(self):
from cli import HermesCLI
cli_obj = HermesCLI.__new__(HermesCLI)
cli_obj._attached_images = [Path("/tmp/stale.png")]
cli_obj._pending_input = queue.Queue()
cli_obj._voice_lock = MagicMock()
cli_obj._voice_processing = True
cli_obj._voice_recording = True
cli_obj._voice_continuous = False
cli_obj._no_speech_count = 0
cli_obj._voice_recorder = MagicMock()
cli_obj._voice_recorder.stop.return_value = "/tmp/fake.wav"
cli_obj._app = None
return cli_obj
def test_voice_transcript_clears_stale_attached_images(self, cli):
with patch("tools.voice_mode.play_beep"):
with patch("tools.voice_mode.transcribe_recording", return_value={"success": True, "transcript": "hello"}):
with patch("os.path.isfile", return_value=False):
with patch("cli._cprint"):
cli._voice_stop_and_transcribe()
assert cli._attached_images == []
queued = cli._pending_input.get_nowait()
# Voice transcripts are wrapped in the _VoiceInputMessage sentinel
# (#65827) so process_loop can distinguish STT output from typed text.
from cli import _VoiceInputMessage
assert isinstance(queued, _VoiceInputMessage)
assert queued.text == "hello"