feat: support complet des images — arborescence, visionneuse, indexation #108
This commit is contained in:
@@ -0,0 +1,105 @@
|
||||
# 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
|
||||
Reference in New Issue
Block a user