54bc384650
Migrazione 014 (batch_alter_table, nome del vincolo esistente letto con sa.inspect invece di indovinato) e stessi ondelete nei modelli ORM. Cancellare un task con esecuzioni o immagini di riferimento non fallisce più su un vincolo FK MySQL. MANCA: i file delle immagini di riferimento su disco non vengono rimossi alla cancellazione del task - tasks.py::delete_task (NON TOCCARE da questo lotto) cancella con `db.delete(task)` diretto, senza un punto unico in vision_service da agganciare. `_copy_tasks_to_version` (recipe_service.py) non copia le immagini di riferimento nel copy-on-write: nemmeno questo si tocca qui. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
81 lines
3.1 KiB
Python
81 lines
3.1 KiB
Python
"""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"
|