fix(products): bomb-safe image processing + per-image error isolation; honest claim/resumability docs (SLICE-7 review)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -53,3 +53,13 @@ def test_not_an_image_rejected_not_image():
|
||||
def test_jpeg_source_format_preserved_in_result():
|
||||
result = images.process(_jpeg(800, 800))
|
||||
assert result.source_format == "JPEG"
|
||||
|
||||
|
||||
def test_decompression_bomb_rejected_not_image(monkeypatch):
|
||||
# A small file whose pixel count exceeds Pillow's bomb threshold must be a
|
||||
# typed rejection, never an escaping exception.
|
||||
from PIL import Image as PILImage
|
||||
monkeypatch.setattr(PILImage, "MAX_IMAGE_PIXELS", 100) # 10x10 exceeds it
|
||||
result = images.process(_png(800, 800))
|
||||
assert isinstance(result, images.Rejected)
|
||||
assert result.reason == "rejected_not_image"
|
||||
|
||||
@@ -121,6 +121,28 @@ def test_run_phase_classifies_each_image(pool, host, tmp_path):
|
||||
assert _run_status(pool, rid) == "complete_with_problems"
|
||||
|
||||
|
||||
def test_unexpected_processing_error_marks_image_failed_not_wedged(pool, host, tmp_path, monkeypatch):
|
||||
# If anything after the fetch raises unexpectedly (e.g. objectstore.put),
|
||||
# the image is marked failed and the run still reaches a terminal status —
|
||||
# never stranded in fetching_images.
|
||||
store = objectstore.LocalObjectStore(str(tmp_path))
|
||||
sf, _acct, rid = _seed(pool)
|
||||
p = _product(pool, sf, "lamp")
|
||||
iid = _image(pool, p, f"{host}/good.png", rid, position=1)
|
||||
|
||||
def _boom(*a, **k):
|
||||
raise RuntimeError("storage down")
|
||||
monkeypatch.setattr(store, "put", _boom)
|
||||
|
||||
imagefetch.run_image_phase(pool, store, rid, allow_private=True)
|
||||
with pool.connection() as conn:
|
||||
counts = repo.run_image_counts(conn, rid)
|
||||
assert counts["failed"] == 1 and counts["pending"] == 0
|
||||
assert _run_status(pool, rid) == "complete_with_problems"
|
||||
row = _img_row(pool, iid)
|
||||
assert row["status"] == "failed"
|
||||
|
||||
|
||||
def test_resume_after_kill_completes_remaining(pool, host, tmp_path):
|
||||
store = objectstore.LocalObjectStore(str(tmp_path))
|
||||
sf, _acct, rid = _seed(pool)
|
||||
|
||||
Reference in New Issue
Block a user