Files
turnstone/tests/test_thumbnails.py
T
Patrick Buckley 4332997d59 feat(attachments): inline chip previews (image/pdf thumbnail, audio player, text snippet)
- core/thumbnails.py + GET .../attachments/{id}/thumbnail: server-rendered PNG
  thumbnails (image downscale; pdf first page via pypdfium2). Extracted a shared
  ownership-gated blob resolver used by both get_content and the thumbnail route
- buildAttachmentPreview (composer_attachments.js): image/pdf -> thumbnail,
  audio -> <audio> player, text -> lazy snippet; reused by the composer chips and
  the sent-message pills (interactive.js). Cookie auth, so direct media src works
- chip kind icons now cover pdf/audio; the upload swap adopts the server's
  authoritative kind for styling + icon + preview
- chat.css preview styling; tests for make_thumbnail
2026-06-16 03:50:31 -07:00

52 lines
1.9 KiB
Python

"""Tests for attachment thumbnail generation (image downscale + pdf first page)."""
from __future__ import annotations
from turnstone.core.thumbnails import make_thumbnail
PNG_1x1 = (
b"\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x01\x00\x00\x00\x01"
b"\x08\x06\x00\x00\x00\x1f\x15\xc4\x89\x00\x00\x00\rIDATx\x9cc\xfc\xcf"
b"\xc0\xc0\xc0\x00\x00\x00\x05\x00\x01\xa5\xf6E@\x00\x00\x00\x00IEND\xaeB`\x82"
)
_PNG_MAGIC = b"\x89PNG\r\n\x1a\n"
def _minimal_pdf(text: str = "Hi") -> bytes:
stream = b"BT /F1 24 Tf 20 60 Td (" + text.encode("latin-1") + b") Tj ET"
objs = [
b"<</Type/Catalog/Pages 2 0 R>>",
b"<</Type/Pages/Kids[3 0 R]/Count 1>>",
b"<</Type/Page/Parent 2 0 R/MediaBox[0 0 300 144]"
b"/Contents 4 0 R/Resources<</Font<</F1 5 0 R>>>>>>",
b"<</Length %d>>\nstream\n%s\nendstream" % (len(stream), stream),
b"<</Type/Font/Subtype/Type1/BaseFont/Helvetica>>",
]
pdf = b"%PDF-1.4\n"
offsets = []
for i, obj in enumerate(objs, 1):
offsets.append(len(pdf))
pdf += b"%d 0 obj\n%s\nendobj\n" % (i, obj)
xref = len(pdf)
pdf += b"xref\n0 %d\n0000000000 65535 f \n" % (len(objs) + 1)
for off in offsets:
pdf += b"%010d 00000 n \n" % off
pdf += b"trailer\n<</Size %d/Root 1 0 R>>\nstartxref\n%d\n%%%%EOF" % (len(objs) + 1, xref)
return pdf
class TestMakeThumbnail:
def test_image_thumbnail_is_png(self) -> None:
out = make_thumbnail(PNG_1x1, "image")
assert out is not None and out[:8] == _PNG_MAGIC
def test_pdf_thumbnail_is_png(self) -> None:
out = make_thumbnail(_minimal_pdf(), "pdf")
assert out is not None and out[:8] == _PNG_MAGIC
def test_audio_has_no_thumbnail(self) -> None:
assert make_thumbnail(b"RIFFfake", "audio") is None
def test_garbage_image_returns_none(self) -> None:
assert make_thumbnail(b"not an image", "image") is None