"""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, food_matches_line, has_extraction_failure, ingredient_display_lines, line_remainder, looks_mangled, match_unit, normalize_for_parser, same_line, slugify, unit_token, ) 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("47⁄10 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"] class TestParserStructureSafety: def test_reads_the_written_unit_token(self): assert unit_token("4,7 dl basmatiris") == "dl" assert unit_token("1-1,5 msk fond") == "msk" assert unit_token("salt") is None def test_matches_instance_unit_by_abbreviation(self): gram = {"id": "g", "name": "gram", "abbreviation": "g"} assert match_unit("g", [gram]) == gram assert match_unit("dl", [gram]) is None def test_rejects_a_nearby_but_different_food(self): assert food_matches_line("kycklinglårfilé", "800 g kycklinglårfilé") assert not food_matches_line("kycklingfilé", "800 g kycklinglårfilé") def test_builds_note_from_only_the_unstructured_remainder(self): assert ( line_remainder( "200 g körsbärstomater (i olika färger)", "g", "körsbärstomat", ) == "(i olika färger)" ) def test_same_line_ignores_only_whitespace(self): assert same_line("3 dl grädde", " 3 dl grädde ") assert not same_line("3 dl grädde", "3 liter grädde")