106 lines
4.0 KiB
Python
106 lines
4.0 KiB
Python
# tests/test_image_indexing.py — Images in the tree & index (roadmap #108-A)
|
|
import asyncio
|
|
import base64
|
|
from pathlib import Path
|
|
|
|
from backend.indexer import _index_single_file_sync, _scan_vault
|
|
from backend.services.vaults import browse_directory
|
|
from backend.watcher import VaultEventHandler
|
|
|
|
PNG_1x1 = base64.b64decode(
|
|
"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAC0lEQVR4nGNgAAIAAAUAAen63NgAAAAASUVORK5CYII="
|
|
)
|
|
|
|
|
|
def _make_vault(tmp_path: Path) -> Path:
|
|
vault = tmp_path / "Vault"
|
|
vault.mkdir()
|
|
(vault / "note.md").write_text("# Note\n", encoding="utf-8")
|
|
(vault / "chatScreenshot.png").write_bytes(PNG_1x1)
|
|
assets = vault / "assets"
|
|
assets.mkdir()
|
|
(assets / "photo.jpg").write_bytes(PNG_1x1)
|
|
return vault
|
|
|
|
|
|
class TestScanVaultImages:
|
|
def test_image_indexed_without_content(self, tmp_path):
|
|
vault = _make_vault(tmp_path)
|
|
result = _scan_vault("V", str(vault))
|
|
by_path = {f["path"]: f for f in result["files"]}
|
|
assert "chatScreenshot.png" in by_path
|
|
image = by_path["chatScreenshot.png"]
|
|
assert image["content"] == "" # never read the bytes (#108-A2)
|
|
assert image["content_preview"] == ""
|
|
assert image["extension"] == ".png"
|
|
assert image["size"] > 0
|
|
assert "assets/photo.jpg" in by_path
|
|
|
|
def test_images_present_in_paths(self, tmp_path):
|
|
vault = _make_vault(tmp_path)
|
|
result = _scan_vault("V", str(vault))
|
|
files = {p["path"] for p in result["paths"] if p["type"] == "file"}
|
|
assert "chatScreenshot.png" in files
|
|
assert "assets/photo.jpg" in files
|
|
|
|
def test_no_binary_content_leaks(self, tmp_path):
|
|
vault = _make_vault(tmp_path)
|
|
result = _scan_vault("V", str(vault))
|
|
for f in result["files"]:
|
|
if f["extension"] in (".png", ".jpg", ".jpeg", ".gif", ".svg", ".webp", ".bmp", ".ico"):
|
|
assert f["content"] == ""
|
|
|
|
|
|
class TestSingleFileImage:
|
|
def test_metadata_only(self, tmp_path):
|
|
vault = _make_vault(tmp_path)
|
|
info = _index_single_file_sync(
|
|
"V", str(vault), str(vault / "chatScreenshot.png")
|
|
)
|
|
assert info is not None
|
|
assert info["content"] == ""
|
|
assert info["extension"] == ".png"
|
|
assert info["path"] == "chatScreenshot.png"
|
|
|
|
|
|
class TestWatcherRelevance:
|
|
def test_image_extensions_relevant(self, tmp_path):
|
|
handler = VaultEventHandler("V", asyncio.Queue(), asyncio.new_event_loop())
|
|
assert handler._is_relevant(str(tmp_path / "a.png")) is True
|
|
assert handler._is_relevant(str(tmp_path / "a.jpg")) is True
|
|
assert handler._is_relevant(str(tmp_path / "a.svg")) is True
|
|
assert handler._is_relevant(str(tmp_path / "a.exe")) is False
|
|
|
|
|
|
class TestBrowseAndDashboard:
|
|
def test_browse_lists_image(self, client):
|
|
resp = client.get("/api/browse/TestVault")
|
|
assert resp.status_code == 200
|
|
items = resp.json()["items"]
|
|
names = {i["name"]: i for i in items}
|
|
assert "chatScreenshot.png" in names
|
|
assert names["chatScreenshot.png"]["type"] == "file"
|
|
assert names["chatScreenshot.png"]["extension"] == ".png"
|
|
|
|
def test_dashboard_counts_images(self, client):
|
|
resp = client.get("/api/dashboard")
|
|
assert resp.status_code == 200
|
|
data = resp.json()
|
|
assert data["total_images"] >= 2
|
|
vault = next(v for v in data["vaults"] if v["name"] == "TestVault")
|
|
assert vault["image_count"] >= 2
|
|
|
|
def test_ext_filter_returns_images(self, client):
|
|
resp = client.get("/api/search/advanced", params={"q": "ext:png"})
|
|
assert resp.status_code == 200
|
|
paths = [r["path"] for r in resp.json()["results"]]
|
|
assert any(p.endswith(".png") for p in paths)
|
|
|
|
|
|
class TestBrowseService:
|
|
def test_browse_directory_service(self, app_with_vault, test_vault_dir):
|
|
# The shared index is populated by the app_with_vault fixture.
|
|
data = browse_directory("TestVault", "")
|
|
names = [i["name"] for i in data["items"]]
|
|
assert "chatScreenshot.png" in names
|