110 lines
5.3 KiB
Python
110 lines
5.3 KiB
Python
# 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
|