Short IDs become the canonical identifier in URLs (/i/:short_id), MinIO/R2 storage keys, and all API responses. Hash-based deduplication is preserved. Includes two-phase Alembic migration (003 adds nullable column, 004 enforces NOT NULL) with a backfill script to copy storage objects and populate short_id for existing images. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
81 lines
2.2 KiB
Python
81 lines
2.2 KiB
Python
"""Unit tests for thumbnail generation utility."""
|
|
|
|
import io
|
|
|
|
from PIL import Image as PILImage
|
|
|
|
from app.thumbnail import generate_thumbnail
|
|
|
|
|
|
def _make_jpeg(width: int, height: int) -> bytes:
|
|
buf = io.BytesIO()
|
|
img = PILImage.new("RGB", (width, height), color=(128, 64, 32))
|
|
img.save(buf, format="JPEG", quality=80)
|
|
return buf.getvalue()
|
|
|
|
|
|
def _make_png_rgba(width: int, height: int) -> bytes:
|
|
buf = io.BytesIO()
|
|
img = PILImage.new("RGBA", (width, height), color=(10, 20, 30, 180))
|
|
img.save(buf, format="PNG")
|
|
return buf.getvalue()
|
|
|
|
|
|
def _make_gif(width: int, height: int) -> bytes:
|
|
buf = io.BytesIO()
|
|
img = PILImage.new("P", (width, height))
|
|
img.save(buf, format="GIF")
|
|
return buf.getvalue()
|
|
|
|
|
|
def test_thumbnail_is_webp():
|
|
data = _make_jpeg(600, 400)
|
|
result = generate_thumbnail(data, "image/jpeg")
|
|
assert result[:4] == b"RIFF"
|
|
assert result[8:12] == b"WEBP"
|
|
|
|
|
|
def test_thumbnail_fits_within_400px():
|
|
data = _make_jpeg(800, 600)
|
|
result = generate_thumbnail(data, "image/jpeg")
|
|
img = PILImage.open(io.BytesIO(result))
|
|
w, h = img.size
|
|
assert w <= 400
|
|
assert h <= 400
|
|
|
|
|
|
def test_thumbnail_preserves_aspect_ratio():
|
|
original_w, original_h = 800, 300
|
|
data = _make_jpeg(original_w, original_h)
|
|
result = generate_thumbnail(data, "image/jpeg")
|
|
img = PILImage.open(io.BytesIO(result))
|
|
w, h = img.size
|
|
original_ratio = original_w / original_h
|
|
thumb_ratio = w / h
|
|
assert abs(original_ratio - thumb_ratio) / original_ratio < 0.01
|
|
|
|
|
|
def test_thumbnail_handles_gif_first_frame():
|
|
data = _make_gif(500, 500)
|
|
result = generate_thumbnail(data, "image/gif")
|
|
assert result[8:12] == b"WEBP"
|
|
img = PILImage.open(io.BytesIO(result))
|
|
assert not getattr(img, "is_animated", False)
|
|
|
|
|
|
def test_thumbnail_handles_png_with_alpha():
|
|
data = _make_png_rgba(300, 300)
|
|
result = generate_thumbnail(data, "image/png")
|
|
assert result[8:12] == b"WEBP"
|
|
img = PILImage.open(io.BytesIO(result))
|
|
assert img.format == "WEBP"
|
|
|
|
|
|
def test_thumbnail_does_not_upscale():
|
|
data = _make_jpeg(100, 100)
|
|
result = generate_thumbnail(data, "image/jpeg")
|
|
img = PILImage.open(io.BytesIO(result))
|
|
w, h = img.size
|
|
assert w <= 100
|
|
assert h <= 100
|