diff --git a/hermes_cli/clipboard.py b/hermes_cli/clipboard.py index 6373cfc8..6fda33bc 100644 --- a/hermes_cli/clipboard.py +++ b/hermes_cli/clipboard.py @@ -292,9 +292,12 @@ def _convert_to_png(path: Path) -> bool: ["convert", str(tmp), "png:" + str(path)], capture_output=True, timeout=5, ) - tmp.unlink(missing_ok=True) if r.returncode == 0 and path.exists() and path.stat().st_size > 0: + tmp.unlink(missing_ok=True) return True + else: + # Convert failed — restore the original file + tmp.rename(path) except FileNotFoundError: logger.debug("ImageMagick not installed — cannot convert BMP to PNG") if tmp.exists() and not path.exists(): diff --git a/tests/tools/test_clipboard.py b/tests/tools/test_clipboard.py index dca3d3d2..19be4012 100644 --- a/tests/tools/test_clipboard.py +++ b/tests/tools/test_clipboard.py @@ -558,6 +558,51 @@ class TestConvertToPng: assert result is True assert dest.exists() and dest.stat().st_size > 0 + def test_imagemagick_failure_preserves_original(self, tmp_path): + """When ImageMagick convert fails, the original file must not be lost.""" + dest = tmp_path / "img.png" + original_data = FAKE_BMP + dest.write_bytes(original_data) + + def fake_run_fail(cmd, **kw): + # Simulate convert failing without producing output + return MagicMock(returncode=1) + + with patch.dict(sys.modules, {"PIL": None, "PIL.Image": None}): + with patch("hermes_cli.clipboard.subprocess.run", side_effect=fake_run_fail): + _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() == original_data + + def test_imagemagick_not_installed_preserves_original(self, tmp_path): + """When ImageMagick is not installed, the original file must not be lost.""" + dest = tmp_path / "img.png" + original_data = FAKE_BMP + dest.write_bytes(original_data) + + with patch.dict(sys.modules, {"PIL": None, "PIL.Image": None}): + with patch("hermes_cli.clipboard.subprocess.run", side_effect=FileNotFoundError): + _convert_to_png(dest) + + assert dest.exists(), "Original file was lost when ImageMagick not installed" + assert dest.read_bytes() == original_data + + def test_imagemagick_timeout_preserves_original(self, tmp_path): + """When ImageMagick times out, the original file must not be lost.""" + import subprocess + dest = tmp_path / "img.png" + original_data = FAKE_BMP + dest.write_bytes(original_data) + + with patch.dict(sys.modules, {"PIL": None, "PIL.Image": None}): + with patch("hermes_cli.clipboard.subprocess.run", side_effect=subprocess.TimeoutExpired("convert", 5)): + _convert_to_png(dest) + + assert dest.exists(), "Original file was lost after timeout" + assert dest.read_bytes() == original_data + # ── has_clipboard_image dispatch ─────────────────────────────────────────