import io import json from pathlib import Path import pytest from PIL import Image from backend.rendering import normalize_image_isolated from backend.validation import InvalidAsset, IMAGE_ERRORS def encoded(size=(64, 64), color=(60, 100, 180, 255), **kwargs): buffer=io.BytesIO() Image.new('RGBA',size,color).save(buffer,'PNG',**kwargs) return buffer.getvalue() @pytest.mark.parametrize('raw,code',[ (b'','decode'), (encoded((16, 200)),'aspect'), (encoded((8, 8)),'dimensions'), (encoded(color=(0, 0, 0, 0)),'transparent'), ]) def test_worker_returns_actionable_allowlisted_errors(tmp_path,raw,code): output=tmp_path/'safe.png' with pytest.raises(InvalidAsset,match='^'+__import__('re').escape(IMAGE_ERRORS[code])+'$'): normalize_image_isolated(raw,output) assert not output.exists() def test_worker_keeps_server_pixel_limit(tmp_path): # Browser preparation does not relax direct API upload limits. with pytest.raises(InvalidAsset,match='safe image limits'): normalize_image_isolated(encoded((4001,4000)),tmp_path/'too-large.png') def test_worker_rejects_animation(tmp_path): b=io.BytesIO();a=Image.new('RGBA',(64,64),'red');z=Image.new('RGBA',(64,64),'blue') a.save(b,'PNG',save_all=True,append_images=[z],duration=100,loop=0) with pytest.raises(InvalidAsset,match='Animated images'): normalize_image_isolated(b.getvalue(),tmp_path/'animated.png') def test_worker_preserves_orientation_and_removes_metadata(tmp_path): b=io.BytesIO();im=Image.new('RGB',(32,64),'red');exif=Image.Exif();exif[274]=6;exif[270]='private fixture metadata' im.save(b,'JPEG',exif=exif) output=tmp_path/'safe.png';result=normalize_image_isolated(b.getvalue(),output) assert (result['width'],result['height'])==(64,32) with Image.open(output) as image:assert image.format=='PNG' and not image.info def test_unrecognized_worker_diagnostic_is_never_returned(tmp_path,monkeypatch): def fake(args,cwd,env,**kwargs): (Path(cwd)/'process.log').write_text(json.dumps({'ok':False,'code':'/private/user-secret'})) monkeypatch.setattr('backend.rendering.bounded_run',fake) with pytest.raises(InvalidAsset,match='^'+__import__('re').escape(IMAGE_ERRORS['decode'])+'$'): normalize_image_isolated(b'bad',tmp_path/'safe.png')