grocery-bot

Log | Files | Refs | README

test_parsing.py (4563B)


      1 from fractions import Fraction
      2 
      3 from parsing import (
      4     Item,
      5     group_by_category,
      6     merge_ingredients,
      7     parse_quantity,
      8     parse_recipe_text,
      9     suggest_keto,
     10 )
     11 
     12 
     13 def test_parse_quantity_whole_number():
     14     assert parse_quantity("2") == Fraction(2)
     15 
     16 
     17 def test_parse_quantity_fraction():
     18     assert parse_quantity("1/2") == Fraction(1, 2)
     19 
     20 
     21 def test_parse_quantity_mixed_number():
     22     assert parse_quantity("1 1/2") == Fraction(3, 2)
     23 
     24 
     25 def test_parse_quantity_unparseable_returns_none():
     26     assert parse_quantity("a pinch") is None
     27 
     28 
     29 def test_merge_sums_matching_name_and_unit():
     30     items = [
     31         Item(name="Milk", quantity="1", unit="cup", category="dairy", source="staple"),
     32         Item(name="milk", quantity="1", unit="cup", category="dairy", source="recipe"),
     33     ]
     34     merged = merge_ingredients(items)
     35     assert len(merged) == 1
     36     assert merged[0].quantity == "2"
     37 
     38 
     39 def test_merge_keeps_mismatched_units_separate():
     40     items = [
     41         Item(name="Flour", quantity="1", unit="cup", category="pantry", source="staple"),
     42         Item(name="Flour", quantity="200", unit="g", category="pantry", source="recipe"),
     43     ]
     44     merged = merge_ingredients(items)
     45     assert len(merged) == 2
     46 
     47 
     48 def test_merge_keeps_unparseable_quantities_separate():
     49     items = [
     50         Item(name="Salt", quantity="a pinch", unit=None, category="pantry", source="recipe"),
     51         Item(name="Salt", quantity="a pinch", unit=None, category="pantry", source="recipe"),
     52     ]
     53     merged = merge_ingredients(items)
     54     assert len(merged) == 2
     55 
     56 
     57 def test_parse_recipe_text_basic():
     58     body = (
     59         "Servings: 4\n"
     60         "Source: test\n"
     61         "Keto: yes\n"
     62         "Ingredients:\n"
     63         "1 | lb | ground beef | meat\n"
     64         "1 | cup | diced tomatoes | pantry\n"
     65         "Instructions:\n"
     66         "Brown the beef.\n"
     67         "Simmer.\n"
     68     )
     69     parsed = parse_recipe_text("Test Chili", body)
     70     assert parsed["name"] == "Test Chili"
     71     assert parsed["servings"] == 4
     72     assert parsed["is_keto"] is True
     73     assert len(parsed["ingredients"]) == 2
     74     assert "Brown the beef." in parsed["instructions"]
     75 
     76 
     77 def test_parse_recipe_text_missing_name_raises():
     78     try:
     79         parse_recipe_text("  ", "Instructions:\nDo stuff.\n")
     80         assert False, "expected ValueError"
     81     except ValueError:
     82         pass
     83 
     84 
     85 def test_parse_recipe_text_freeform_real_world_paste():
     86     # Shape of a recipe copy-pasted straight from a blog: no header before
     87     # ingredients, freeform ingredient lines (no "|"), a bare "INSTRUCTIONS"
     88     # header with no colon, and a trailing "NUTRITION" section to discard.
     89     body = (
     90         "2 tablespoons pork lard, or butter\n"
     91         "3 pounds chicken pieces, bone-in and skin-on\n"
     92         "2 medium yellow onions, very finely chopped\n"
     93         "\n"
     94         "INSTRUCTIONS\n"
     95         "\n"
     96         "Heat the lard in a large Dutch oven and brown the chicken.\n"
     97         "Add the onions and fry until golden brown.\n"
     98         "\n"
     99         "NUTRITION\n"
    100         "Calories: 500\n"
    101     )
    102     parsed = parse_recipe_text("Chicken Paprikash", body)
    103     assert parsed["name"] == "Chicken Paprikash"
    104     assert len(parsed["ingredients"]) == 3
    105     assert parsed["ingredients"][0]["name"] == "2 tablespoons pork lard, or butter"
    106     assert parsed["ingredients"][0]["quantity"] is None
    107     assert "brown the chicken" in parsed["instructions"]
    108     assert "Calories" not in parsed["instructions"]
    109 
    110 
    111 def test_suggest_keto_flags_high_carb_ingredient():
    112     assert suggest_keto(["ground beef", "flour"]) is False
    113 
    114 
    115 def test_suggest_keto_true_when_no_high_carb_keywords():
    116     assert suggest_keto(["ground beef", "cheese"]) is True
    117 
    118 
    119 def test_group_by_category_groups_and_puts_uncategorized_last():
    120     items = [
    121         Item(name="Milk", quantity="1", unit="cup", category="dairy", source="staple"),
    122         Item(name="Onion", quantity="1", unit=None, category=None, source="recipe"),
    123         Item(name="Cheese", quantity="1", unit="cup", category="dairy", source="staple"),
    124         Item(name="Flour", quantity="1", unit="cup", category="pantry", source="staple"),
    125     ]
    126     grouped = group_by_category(items)
    127     categories = [cat for cat, _ in grouped]
    128     assert categories == ["dairy", "pantry", None]
    129     assert [i.name for i in dict(grouped)["dairy"]] == ["Milk", "Cheese"]
    130 
    131 
    132 def test_group_by_category_no_uncategorized_bucket_when_none_missing():
    133     items = [
    134         Item(name="Milk", quantity="1", unit="cup", category="dairy", source="staple"),
    135     ]
    136     grouped = group_by_category(items)
    137     assert grouped == [("dairy", items)]