diff --git a/backend/app/tasks/thumbnail.py b/backend/app/tasks/thumbnail.py index dec644f..ab465ce 100644 --- a/backend/app/tasks/thumbnail.py +++ b/backend/app/tasks/thumbnail.py @@ -17,6 +17,30 @@ from ._sync_engine import sync_session_factory as _sync_session_factory IMAGES_ROOT = Path("/images") +THUMB_MAGIC_JPEG = b"\xff\xd8\xff" +THUMB_MAGIC_PNG = b"\x89PNG\r\n\x1a\n" + + +def _thumb_is_valid(path: Path) -> bool: + """Return True iff `path` exists and starts with a JPEG or PNG magic header. + + The on-disk thumbnail format is set by services/thumbnailer.py — JPEG for + opaque sources, PNG for alpha sources. Anything else (missing file, OSError, + truncated, wrong magic) is invalid. + """ + try: + with path.open("rb") as f: + head = f.read(12) + except OSError: + return False + if len(head) < 8: + return False + if head[:3] == THUMB_MAGIC_JPEG: + return True + if head[:8] == THUMB_MAGIC_PNG: + return True + return False + @celery.task( name="backend.app.tasks.thumbnail.generate_thumbnail", diff --git a/tests/test_backfill_thumbnails.py b/tests/test_backfill_thumbnails.py new file mode 100644 index 0000000..5537577 --- /dev/null +++ b/tests/test_backfill_thumbnails.py @@ -0,0 +1,35 @@ +"""Thumbnail backfill: _thumb_is_valid helper + backfill_thumbnails planner.""" + +import pytest + +from backend.app.tasks.thumbnail import _thumb_is_valid + +pytestmark = pytest.mark.integration + + +def test_thumb_is_valid_jpeg(tmp_path): + p = tmp_path / "good.jpg" + p.write_bytes(b"\xff\xd8\xff\xe0" + b"\x00" * 100) + assert _thumb_is_valid(p) is True + + +def test_thumb_is_valid_png(tmp_path): + p = tmp_path / "good.png" + p.write_bytes(b"\x89PNG\r\n\x1a\n" + b"\x00" * 100) + assert _thumb_is_valid(p) is True + + +def test_thumb_is_valid_too_short(tmp_path): + p = tmp_path / "tiny" + p.write_bytes(b"\xff\xd8") + assert _thumb_is_valid(p) is False + + +def test_thumb_is_valid_wrong_magic(tmp_path): + p = tmp_path / "garbage" + p.write_bytes(b"\x00" * 12) + assert _thumb_is_valid(p) is False + + +def test_thumb_is_valid_missing_file(tmp_path): + assert _thumb_is_valid(tmp_path / "nope") is False