1 Commits

Author SHA1 Message Date
fredamn76 2846289df1 Treat Mealie's "no image" sentinel as a missing cover
Mealie stores the literal string "no image" in the recipe's image field when
there is no cover, so bool(recipe["image"]) reported a cover that is not
there: the check meant to catch a missing image downgraded to a warning
instead of failing. Observed on a recipe created through Mealie's own image
import, which returned image: "no image".

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-18 12:55:27 +02:00
2 changed files with 26 additions and 1 deletions
+18 -1
View File
@@ -16,6 +16,10 @@ from .normalize import (
looks_mangled, looks_mangled,
) )
#: Mealie stores this sentinel in ``image`` when a recipe has no cover, so a
#: truthiness test on the field reports a cover that is not there.
NO_IMAGE_SENTINEL = "no image"
Status = Literal["pass", "fail", "warn"] Status = Literal["pass", "fail", "warn"]
@@ -23,6 +27,19 @@ def _check(check_id: str, status: Status, detail: str) -> dict[str, str]:
return {"check": check_id, "status": status, "detail": detail} return {"check": check_id, "status": status, "detail": detail}
def has_cover_image(recipe: dict[str, Any]) -> bool:
"""Whether the recipe carries a real cover image.
Mealie writes the literal string ``"no image"`` instead of an empty value
when a recipe has no cover, so an emptiness test has to reject that
sentinel too.
"""
image = recipe.get("image")
if not isinstance(image, str):
return bool(image)
return image.strip().casefold() not in ("", NO_IMAGE_SENTINEL)
def is_parsed(recipe: dict[str, Any]) -> bool: def is_parsed(recipe: dict[str, Any]) -> bool:
"""Whether Mealie will stop showing 'your ingredients aren't parsed yet'. """Whether Mealie will stop showing 'your ingredients aren't parsed yet'.
@@ -149,7 +166,7 @@ def verify_recipe(
) )
) )
has_image_field = bool(recipe.get("image")) has_image_field = has_cover_image(recipe)
if image_verified: if image_verified:
image_status: Status = "pass" image_status: Status = "pass"
image_detail = "Image step ran and was verified by the caller" image_detail = "Image step ran and was verified by the caller"
+8
View File
@@ -61,6 +61,14 @@ class TestCoverImage:
assert status_of(report, "cover_image") == "fail" assert status_of(report, "cover_image") == "fail"
assert report["complete"] is False assert report["complete"] is False
@pytest.mark.parametrize("stored", ["no image", "No Image", " no image ", ""])
def test_mealies_no_image_sentinel_fails(self, stored):
# Verified against Mealie 3.22: a recipe without a cover stores the
# string "no image", which a truthiness test reads as a cover present.
report = verify_recipe(complete_recipe(image=stored))
assert status_of(report, "cover_image") == "fail"
assert report["complete"] is False
class TestSwedishContent: class TestSwedishContent:
def test_english_ingredient_line_fails(self): def test_english_ingredient_line_fails(self):