"""Il grafo di visione vive sul task, e la quota sa quale uscita la riempie.""" import pytest from sqlalchemy import select from src.backend.models.orm.recipe import RecipeVersion from src.backend.models.orm.task import TASK_TYPES, RecipeSubtask, RecipeTask from src.backend.models.orm.vision import VisionReferenceImage, VisionResult from src.backend.tests.conftest import create_test_recipe async def recipe_parts(db_session, recipe_id: int): """`create_test_recipe` restituisce la sola ricetta: qui si scompone. Versione corrente, il suo unico task, la sua prima quota. È l'idioma che `test_out_of_tolerance.py` usa già, ripetuto perché ogni file di prova si legge da solo. """ version = (await db_session.execute( select(RecipeVersion).where( RecipeVersion.recipe_id == recipe_id, RecipeVersion.is_current == True, # noqa: E712 ) )).scalar_one() task = (await db_session.execute( select(RecipeTask).where(RecipeTask.version_id == version.id) )).scalars().first() subtask = (await db_session.execute( select(RecipeSubtask).where(RecipeSubtask.task_id == task.id) )).scalars().first() return version, task, subtask @pytest.mark.asyncio async def test_task_carries_a_vision_graph(db_session, maker_user): """Il grafo si salva e si rilegge identico: il server non lo interpreta.""" recipe = await create_test_recipe(db_session, maker_user.id, code="VIS-1") version, task, subtask = await recipe_parts(db_session, recipe.id) graph = { "schema_version": 1, "name": "diametro del foro", "tools": [{"id": "t1", "kind": "DetectCircle", "name": "foro"}], } task.vision_json = graph task.task_type = "camera_measure" await db_session.flush() db_session.expunge_all() reloaded = (await db_session.execute( select(RecipeTask).where(RecipeTask.id == task.id) )).scalar_one() assert reloaded.vision_json == graph @pytest.mark.asyncio async def test_subtask_names_the_output_that_fills_it(db_session, maker_user): """La quota dichiara quale uscita del grafo le compete.""" recipe = await create_test_recipe(db_session, maker_user.id, code="VIS-2") version, task, subtask = await recipe_parts(db_session, recipe.id) subtask.vision_output = "t1.diameter" await db_session.flush() db_session.expunge_all() reloaded = (await db_session.execute( select(RecipeSubtask).where(RecipeSubtask.id == subtask.id) )).scalar_one() assert reloaded.vision_output == "t1.diameter" def test_the_typo_is_gone(): """`xf_compare` era un refuso: si corregge finché nessuna riga lo usa.""" assert "dxf_compare" in TASK_TYPES assert "xf_compare" not in TASK_TYPES @pytest.mark.parametrize("model", [VisionResult, VisionReferenceImage]) def test_task_id_cascades_when_the_task_is_deleted(model): """#38.6: cancellare un task con esecuzioni/immagini non deve fallire su un vincolo FK MySQL - la riga di visione deve seguirlo.""" (fk,) = model.__table__.columns["task_id"].foreign_keys assert fk.ondelete == "CASCADE"