feat: support complet des images — arborescence, visionneuse, indexation #108
This commit is contained in:
@@ -0,0 +1,109 @@
|
||||
# tests/test_image_api.py — Image serving & viewer API (roadmap #108-B/C)
|
||||
import base64
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
PNG_1x1 = base64.b64decode(
|
||||
"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAC0lEQVR4nGNgAAIAAAUAAen63NgAAAAASUVORK5CYII="
|
||||
)
|
||||
SVG_DOC = b'<svg xmlns="http://www.w3.org/2000/svg" width="2" height="2"><script>alert(1)</script></svg>'
|
||||
|
||||
|
||||
def _write_image(vault_dir: str, name: str, content: bytes) -> None:
|
||||
(Path(vault_dir) / name).write_bytes(content)
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════
|
||||
# /api/image — byte serving (BUG fixed in #108-B1)
|
||||
# ═══════════════════════════════════════════════════════════════════
|
||||
|
||||
class TestImageEndpoint:
|
||||
def test_serves_bytes_and_mime(self, client, test_vault_dir):
|
||||
_write_image(test_vault_dir, "pic.png", PNG_1x1)
|
||||
resp = client.get("/api/image/TestVault", params={"path": "pic.png"})
|
||||
assert resp.status_code == 200
|
||||
assert resp.headers["content-type"].startswith("image/png")
|
||||
assert resp.content == PNG_1x1
|
||||
|
||||
def test_missing_image_404(self, client):
|
||||
resp = client.get("/api/image/TestVault", params={"path": "nope.png"})
|
||||
assert resp.status_code == 404
|
||||
|
||||
def test_svg_gets_sandbox_header(self, client, test_vault_dir):
|
||||
_write_image(test_vault_dir, "vector.svg", SVG_DOC)
|
||||
resp = client.get("/api/image/TestVault", params={"path": "vector.svg"})
|
||||
assert resp.status_code == 200
|
||||
assert resp.headers.get("content-security-policy") == "sandbox"
|
||||
assert resp.headers.get("x-content-type-options") == "nosniff"
|
||||
assert resp.content == SVG_DOC
|
||||
|
||||
def test_png_keeps_the_global_csp(self, client, test_vault_dir):
|
||||
_write_image(test_vault_dir, "pic2.png", PNG_1x1)
|
||||
resp = client.get("/api/image/TestVault", params={"path": "pic2.png"})
|
||||
csp = resp.headers.get("content-security-policy", "")
|
||||
assert csp != "sandbox"
|
||||
assert "default-src" in csp
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════
|
||||
# /api/file — standalone image view points at /api/image, not /raw
|
||||
# ═══════════════════════════════════════════════════════════════════
|
||||
|
||||
class TestImageViewMetadata:
|
||||
def test_file_view_uses_image_endpoint(self, client):
|
||||
resp = client.get("/api/file/TestVault", params={"path": "chatScreenshot.png"})
|
||||
assert resp.status_code == 200
|
||||
data = resp.json()
|
||||
assert data["is_image"] is True
|
||||
assert data["image_mime"] == "image/png"
|
||||
assert data["size_bytes"] > 0
|
||||
assert "/api/image/TestVault?path=chatScreenshot.png" in data["html"]
|
||||
# The JSON raw endpoint must NOT be used as an <img> source.
|
||||
assert "/raw?path=" not in data["html"]
|
||||
|
||||
def test_file_view_url_encoded(self, client, test_vault_dir):
|
||||
_write_image(test_vault_dir, "café image.png", PNG_1x1)
|
||||
resp = client.get("/api/file/TestVault", params={"path": "café image.png"})
|
||||
assert resp.status_code == 200
|
||||
html = resp.json()["html"]
|
||||
assert "/api/image/TestVault?path=caf%C3%A9%20image.png" in html
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════
|
||||
# /api/media/{vault}/thumb — thumbnails (roadmap #108-C)
|
||||
# ═══════════════════════════════════════════════════════════════════
|
||||
|
||||
class TestThumbnailEndpoint:
|
||||
def test_png_thumbnail_is_webp(self, client, tmp_path, monkeypatch):
|
||||
pytest.importorskip("PIL")
|
||||
monkeypatch.setenv("OBSIGATE_DATA_DIR", str(tmp_path / "data"))
|
||||
resp = client.get(
|
||||
"/api/media/TestVault/thumb",
|
||||
params={"path": "chatScreenshot.png", "size": 64},
|
||||
)
|
||||
assert resp.status_code == 200
|
||||
assert resp.headers["content-type"].startswith("image/webp")
|
||||
|
||||
def test_svg_thumbnail_falls_back_to_original(self, client, tmp_path, monkeypatch):
|
||||
monkeypatch.setenv("OBSIGATE_DATA_DIR", str(tmp_path / "data"))
|
||||
resp = client.get(
|
||||
"/api/media/TestVault/thumb",
|
||||
params={"path": "vector-icon.svg"},
|
||||
)
|
||||
assert resp.status_code == 200
|
||||
assert "svg" in resp.headers["content-type"]
|
||||
|
||||
def test_thumb_rejects_non_image(self, client):
|
||||
resp = client.get(
|
||||
"/api/media/TestVault/thumb",
|
||||
params={"path": "config.json"},
|
||||
)
|
||||
assert resp.status_code == 400
|
||||
|
||||
def test_missing_thumb_404(self, client):
|
||||
resp = client.get(
|
||||
"/api/media/TestVault/thumb",
|
||||
params={"path": "nope.png"},
|
||||
)
|
||||
assert resp.status_code == 404
|
||||
Reference in New Issue
Block a user