Files
mealie-mcp/tests/test_normalize.py
T
Fizz f2d233b430 Initial commit: Mealie MCP server
Ports a working Hermes agent skill to an MCP server. The skill's value was not
its Mealie endpoints but ~25 operational rules found by running imports against
the live instance; those are now code with tests rather than prompt text.

The server owns deterministic mechanics — auth, the non-default User-Agent
Cloudflare requires, the `extension` field on cover uploads, PATCH instead of
the 500-ing bulk-action endpoints, decimal-comma normalization, and restoring
Swedish display text after parsing. Language and taste judgement stay with the
model, fed by the four reference notes shipped as MCP resources.

verify_recipe runs the finished-import definition as code so an agent cannot
report success on a recipe with an English ingredient line, a missing cover, or
ingredients still in the "Click Parse" state.

Home Assistant is optional and isolated; the Obsidian meal plan is out of scope.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-31 07:31:10 +02:00

100 lines
3.3 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Cases taken from failures verified against the live Mealie instance."""
from __future__ import annotations
import pytest
from mealie_mcp.normalize import (
clean_title,
find_english_leftovers,
has_extraction_failure,
ingredient_display_lines,
looks_mangled,
normalize_for_parser,
slugify,
)
class TestNormalizeForParser:
def test_converts_swedish_decimal_comma(self):
assert normalize_for_parser("4,7 dl basmatiris") == "4.7 dl basmatiris"
def test_leaves_list_commas_alone(self):
assert normalize_for_parser("salt, peppar") == "salt, peppar"
def test_collapses_whitespace(self):
assert normalize_for_parser(" 2 msk olja ") == "2 msk olja"
def test_is_idempotent(self):
once = normalize_for_parser("0,6 dl grädde")
assert normalize_for_parser(once) == once
class TestCleanTitle:
@pytest.mark.parametrize(
"raw,expected",
[
("Fläskfilé med svampsås - se & gör", "Fläskfilé med svampsås"),
("Kycklinggryta | Köket.se", "Kycklinggryta"),
("Pannkakor - se och gör", "Pannkakor"),
("Lax i ugn", "Lax i ugn"),
],
)
def test_strips_campaign_suffixes(self, raw, expected):
assert clean_title(raw) == expected
def test_strips_stacked_suffixes(self):
assert clean_title("Ugnslax - se & gör | Köket.se") == "Ugnslax"
class TestSlugify:
def test_transliterates_swedish_characters(self):
assert slugify("Kycklinggryta med äpple och lök") == "kycklinggryta-med-apple-och-lok"
def test_expands_ampersand(self):
assert slugify("Förrätter & tilltugg") == "forratter-och-tilltugg"
class TestFindEnglishLeftovers:
def test_flags_untranslated_units(self):
assert set(find_english_leftovers("2 cups flour, 1 tbsp butter")) == {"cups", "tbsp"}
def test_clean_swedish_line_passes(self):
assert find_english_leftovers("2 dl vetemjöl, 1 msk smör") == []
def test_does_not_match_inside_swedish_words(self):
# "klyfta" contains no English token; substring matching would be wrong here.
assert find_english_leftovers("1 klyfta vitlök") == []
class TestLooksMangled:
def test_flags_composed_fraction_from_parser(self):
assert looks_mangled("4710 liter basmatiris")
def test_flags_duplicated_unit(self):
assert looks_mangled("2 dl dl grädde")
def test_clean_line_is_not_mangled(self):
assert not looks_mangled("4,7 dl basmatiris")
class TestExtractionFailure:
def test_detects_scraper_placeholder_in_ingredients(self):
recipe = {"recipeIngredient": [{"display": "Could not detect ingredients"}]}
assert has_extraction_failure(recipe)
def test_clean_recipe_passes(self):
recipe = {"recipeIngredient": [{"display": "2 dl grädde"}]}
assert not has_extraction_failure(recipe)
class TestIngredientDisplayLines:
def test_prefers_display_then_note_then_food(self):
recipe = {
"recipeIngredient": [
{"display": "2 dl grädde", "note": "ignored"},
{"display": "", "note": "1 gul lök"},
{"display": "", "note": "", "food": {"name": "salt"}},
]
}
assert ingredient_display_lines(recipe) == ["2 dl grädde", "1 gul lök", "salt"]