Ricetta: il digest della cartella copre anche i file di meta (#46)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-07 12:27:04 +02:00
parent 86290274e5
commit fc1902a1bb
2 changed files with 26 additions and 1 deletions
+9 -1
View File
@@ -261,7 +261,15 @@ def store_recipe_folder(
is left untouched (immutable: other task versions may reference it).
"""
canonical = json.dumps(recipe, sort_keys=True, separators=(",", ":"))
digest = hashlib.sha256(canonical.encode("utf-8")).hexdigest()[:16]
# Il digest copre anche i file (path ordinato + sha256 del contenuto): la
# stessa ricetta con file diversi (es. primo upload senza il DXF) deve dare
# una cartella diversa, altrimenti quella incompleta resterebbe per sempre
# perche' immutabile (#46).
h = hashlib.sha256(canonical.encode("utf-8"))
for rel in sorted(files):
h.update(b"\0" + rel.encode("utf-8") + b"\0")
h.update(hashlib.sha256(files[rel]).digest())
digest = h.hexdigest()[:16]
relative = f"vision/recipes/{task_id}/{digest}"
folder = settings.upload_path / relative
if folder.exists():
@@ -263,3 +263,20 @@ async def test_copy_on_write_carries_recipe_dir(client, db_session, maker_user):
version, new_task, _ = await _current_task_and_subtask(db_session, recipe.id)
assert version.version_number == 2
assert new_task.vision_recipe_dir == "vision/recipes/1/abc" and new_task.vision_json == RECIPE
@pytest.mark.asyncio
async def test_retry_with_missing_file_then_complete_succeeds(
client, db_session, maker_user, monkeypatch,
):
# worker finto: ok solo se il DXF esiste nella cartella della ricetta
async def fake(graph, recipe_dir):
present = (settings.upload_path / recipe_dir / "dxf" / "part.dxf").is_file()
return {"ok": present, "errors": [] if present else ["dxf missing"], "outputs": OUTPUTS}
monkeypatch.setattr("src.backend.services.vision_service.validate_recipe", fake)
task, _ = await make_task(db_session, maker_user)
first = await upload(client, maker_user, task, files=[])
assert first.status_code == 422
second = await upload(client, maker_user, task, files=GOOD_FILES)
assert second.status_code == 200, second.text