"""platform/images — pure decode + resolution bar + WebP renditions (SD-0002 §6.2).""" import io from PIL import Image from app.platform import images def _png(width: int, height: int, color=(120, 80, 200)) -> bytes: buf = io.BytesIO() Image.new("RGB", (width, height), color).save(buf, format="PNG") return buf.getvalue() def _jpeg(width: int, height: int) -> bytes: buf = io.BytesIO() Image.new("RGB", (width, height), (10, 200, 90)).save(buf, format="JPEG") return buf.getvalue() def test_good_image_produces_three_webp_renditions(): result = images.process(_png(1200, 900)) assert isinstance(result, images.Processed) assert result.source_format == "PNG" assert set(result.renditions) == {"thumb", "card", "detail"} for name, blob in result.renditions.items(): with Image.open(io.BytesIO(blob)) as im: assert im.format == "WEBP" with Image.open(io.BytesIO(result.renditions["thumb"])) as im: assert max(im.size) <= images.RENDITIONS["thumb"] with Image.open(io.BytesIO(result.renditions["detail"])) as im: assert max(im.size) <= images.RENDITIONS["detail"] def test_downscale_only_never_upscales_small_source(): result = images.process(_png(520, 520)) with Image.open(io.BytesIO(result.renditions["detail"])) as im: assert im.size == (520, 520) def test_below_resolution_bar_rejected_low_res(): result = images.process(_png(300, 1200)) # shorter side 300 < 500 assert isinstance(result, images.Rejected) assert result.reason == "rejected_low_res" def test_not_an_image_rejected_not_image(): result = images.process(b"this is not an image") assert isinstance(result, images.Rejected) assert result.reason == "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"