From 7aa7f5a3d69b2bed2bda9f2011d1c7503a09524d Mon Sep 17 00:00:00 2001 From: Bryan Van Deusen Date: Tue, 26 May 2026 10:35:06 -0400 Subject: [PATCH] =?UTF-8?q?feat(thumb-backfill):=20=5Fthumb=5Fis=5Fvalid?= =?UTF-8?q?=20helper=20=E2=80=94=20JPEG/PNG=20magic-byte=20check=20on=20th?= =?UTF-8?q?e=20on-disk=20thumbnail=20file?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.7 (1M context) --- backend/app/tasks/thumbnail.py | 24 +++++++++++++++++++++ tests/test_backfill_thumbnails.py | 35 +++++++++++++++++++++++++++++++ 2 files changed, 59 insertions(+) create mode 100644 tests/test_backfill_thumbnails.py 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