"""#99: the vision routes accept BMP (the Opto bench cameras save it); TIFF and non-images stay refused. files.py (attachments) is not touched by this.""" import io import pytest from PIL import Image from sqlalchemy import select from src.backend.config import settings from src.backend.models.orm.vision import VisionReferenceImage from src.backend.tests.conftest import auth_headers, create_test_recipe from src.backend.tests.test_vision_upload_limits import ( # noqa: F401 - fixtures recipe_parts, redirect_uploads, worker_says, ) def a_bmp() -> bytes: buffer = io.BytesIO() Image.new("L", (64, 64), color=0).save(buffer, format="BMP") return buffer.getvalue() def a_tiff() -> bytes: buffer = io.BytesIO() Image.new("L", (64, 64), color=0).save(buffer, format="TIFF") return buffer.getvalue() async def a_camera_task(db_session, user): recipe = await create_test_recipe(db_session, user.id) _, task, _ = await recipe_parts(db_session, recipe.id) task.task_type = "camera_measure" task.vision_json = {"schema_version": 1, "name": "p", "tools": []} await db_session.commit() return task @pytest.mark.asyncio @pytest.mark.parametrize("content_type", ["image/bmp", "image/x-ms-bmp"]) async def test_preview_accepts_a_bmp( client, db_session, maker_user, worker_says, content_type, ): task = await a_camera_task(db_session, maker_user) worker_says({"t1.diameter": 10.0}) response = await client.post( "/api/vision/preview", headers=auth_headers(maker_user), files={"image": ("p.bmp", a_bmp(), content_type)}, data={"task_id": str(task.id)}, ) assert response.status_code != 400, response.text assert response.status_code != 422, response.text @pytest.mark.asyncio async def test_preview_still_rejects_a_tiff( client, db_session, maker_user, worker_says, ): task = await a_camera_task(db_session, maker_user) worker_says({"t1.diameter": 10.0}) response = await client.post( "/api/vision/preview", headers=auth_headers(maker_user), files={"image": ("p.tif", a_tiff(), "image/tiff")}, data={"task_id": str(task.id)}, ) assert response.status_code == 400 @pytest.mark.asyncio async def test_a_text_file_declared_bmp_is_refused_on_its_bytes( client, db_session, maker_user, worker_says, ): task = await a_camera_task(db_session, maker_user) worker_says({"t1.diameter": 10.0}) response = await client.post( "/api/vision/preview", headers=auth_headers(maker_user), files={"image": ("p.bmp", b"i am not an image", "image/bmp")}, data={"task_id": str(task.id)}, ) assert response.status_code == 422 @pytest.mark.asyncio async def test_reference_image_bmp_is_saved_with_the_bmp_extension( client, db_session, maker_user, ): recipe = await create_test_recipe(db_session, maker_user.id) _, task, _ = await recipe_parts(db_session, recipe.id) await db_session.commit() response = await client.post( "/api/vision/reference-images", headers=auth_headers(maker_user), files={"image": ("p.bmp", a_bmp(), "image/x-ms-bmp")}, data={"task_id": str(task.id)}, ) assert response.status_code == 200, response.text saved = (await db_session.execute(select(VisionReferenceImage))).scalars().one() assert saved.path.endswith(".bmp") assert (settings.upload_path / saved.path).exists()