"""Unit tests for navi.tools.image_view."""
import io
from pathlib import Path
from unittest.mock import AsyncMock, patch
import pytest
from PIL import Image
from navi.tools.image_view import ImageViewTool
def _make_image_bytes(mode="RGB", size=(100, 80), fmt="PNG") -> bytes:
img = Image.new(mode, size, color=(255, 0, 0))
buf = io.BytesIO()
img.save(buf, format=fmt)
return buf.getvalue()
# ── _preprocess tests ────────────────────────────────────────────────────────
class TestPreprocess:
def test_keeps_small_image(self):
raw = _make_image_bytes(size=(100, 80))
processed, mime = ImageViewTool._preprocess(raw)
assert mime == "image/jpeg"
img = Image.open(io.BytesIO(processed))
assert img.size == (100, 80)
def test_resizes_large_image(self):
raw = _make_image_bytes(size=(2000, 1500))
processed, mime = ImageViewTool._preprocess(raw)
assert mime == "image/jpeg"
img = Image.open(io.BytesIO(processed))
assert max(img.size) == 1024
def test_converts_rgba_to_rgb(self):
raw = _make_image_bytes(mode="RGBA", size=(50, 50))
processed, mime = ImageViewTool._preprocess(raw)
img = Image.open(io.BytesIO(processed))
assert img.mode == "RGB"
# ── execute tests ────────────────────────────────────────────────────────────
class TestExecute:
@pytest.mark.asyncio
async def test_read_file_success(self, tmp_path: Path):
img_path = tmp_path / "test.png"
img_path.write_bytes(_make_image_bytes(size=(100, 80)))
tool = ImageViewTool()
result = await tool.execute({"source": str(img_path)})
assert result.success is True
assert "Image loaded" in result.output
assert result.metadata["mime"] == "image/jpeg"
assert "base64" in result.metadata
@pytest.mark.asyncio
async def test_mime_guard_rejects_non_image(self, tmp_path: Path):
txt_path = tmp_path / "test.txt"
txt_path.write_text("not an image")
tool = ImageViewTool()
result = await tool.execute({"source": str(txt_path)})
assert result.success is False
assert "Unsupported" in str(result.error)
@pytest.mark.asyncio
async def test_fetch_url_success(self):
tool = ImageViewTool()
raw = _make_image_bytes(size=(100, 80))
from unittest.mock import MagicMock
mock_response = MagicMock()
mock_response.headers = {"content-type": "image/png"}
mock_response.content = raw
mock_response.raise_for_status = MagicMock()
mock_client = AsyncMock()
mock_client.__aenter__ = AsyncMock(return_value=mock_client)
mock_client.__aexit__ = AsyncMock(return_value=False)
mock_client.get = AsyncMock(return_value=mock_response)
with patch("navi.tools.image_view.httpx.AsyncClient", return_value=mock_client):
result = await tool.execute({"source": "https://example.com/img.png"})
assert result.success is True
assert "Image loaded" in result.output
assert result.metadata["mime"] == "image/jpeg"
@pytest.mark.asyncio
async def test_fetch_url_rejects_svg(self):
from unittest.mock import MagicMock
tool = ImageViewTool()
mock_response = MagicMock()
mock_response.headers = {"content-type": "image/svg+xml"}
mock_response.content = b"<svg></svg>"
mock_response.raise_for_status = MagicMock()
mock_client = AsyncMock()
mock_client.__aenter__ = AsyncMock(return_value=mock_client)
mock_client.__aexit__ = AsyncMock(return_value=False)
mock_client.get = AsyncMock(return_value=mock_response)
with patch("navi.tools.image_view.httpx.AsyncClient", return_value=mock_client):
result = await tool.execute({"source": "https://example.com/img.svg"})
assert result.success is False
assert "SVG" in str(result.error)