"""Rezeptzutaten gegen den Bestand: Kann ich das kochen? Ohne Mealie – die Zutaten stehen hier so im Test, wie Mealie sie liefern würde. """ import pytest from app.models import ( BaseUnit, Category, CategoryMeasure, CategoryTracking, Group, Measure, Product, ) from app.services.rezepte import faktor_fuer_portionen, kochbar, pruefe_zutaten from app.services.stock import check_in def _zutat(name, menge=None, einheit=None, text=None): """Eine Zutat im Format von Mealie.""" return { "quantity": menge, "unit": {"name": einheit, "abbreviation": einheit} if einheit else None, "food": {"name": name}, "display": text or f"{menge or ''} {einheit or ''} {name}".strip(), } def _kategorie(db, name): cat = Category(name=name, tracking=CategoryTracking.food.value) db.add(cat) db.commit() db.refresh(cat) return cat def _artikel(db, name, cat, base=BaseUnit.gram, package_size=1000, bestand=0.0): product = Product(name=name, base_unit=base, package_size=package_size, category_id=cat.id, source="manual") db.add(product) db.commit() db.refresh(product) if bestand: check_in(db, product, bestand, base.value, None, None, None) db.commit() return product # -------------------------------------------------------------------------- # Portionen # -------------------------------------------------------------------------- def test_portionen_skalieren_die_mengen(db): cat = _kategorie(db, "Trockenwaren") _artikel(db, "Weissmehl", cat, bestand=5000) faktor, hinweis = faktor_fuer_portionen({"recipeServings": 4}, 6) assert faktor == 1.5 assert hinweis == "" [zutat] = pruefe_zutaten(db, [_zutat("Weissmehl", 200, "g")], faktor=faktor) assert zutat.menge == 300 assert zutat.benoetigt_base == 300 def test_ohne_portionsangabe_wird_nicht_skaliert(db): faktor, hinweis = faktor_fuer_portionen({"recipeServings": 0}, 6) assert faktor == 1.0 assert "keine Portionszahl" in hinweis def test_ohne_wunsch_bleibt_es_wie_im_rezept(db): assert faktor_fuer_portionen({"recipeServings": 4}, None) == (1.0, "") # -------------------------------------------------------------------------- # Reicht der Bestand? # -------------------------------------------------------------------------- def test_genug_vorhanden(db): cat = _kategorie(db, "Trockenwaren") _artikel(db, "Weissmehl", cat, bestand=1000) [zutat] = pruefe_zutaten(db, [_zutat("Weissmehl", 200, "g")]) assert zutat.status == "vorhanden" assert zutat.fehlt_base == 0 assert kochbar([zutat]) is True def test_zu_wenig_meldet_die_fehlmenge(db): cat = _kategorie(db, "Trockenwaren") _artikel(db, "Weissmehl", cat, bestand=150) [zutat] = pruefe_zutaten(db, [_zutat("Weissmehl", 500, "g")]) assert zutat.status == "zu_wenig" assert zutat.fehlt_base == 350 assert kochbar([zutat]) is False def test_gar_nichts_da(db): cat = _kategorie(db, "Trockenwaren") _artikel(db, "Weissmehl", cat, bestand=0) [zutat] = pruefe_zutaten(db, [_zutat("Weissmehl", 500, "g")]) assert zutat.status == "fehlt" assert zutat.fehlt_base == 500 def test_unbekannte_zutat_wird_nicht_still_als_vorhanden_gewertet(db): _kategorie(db, "Trockenwaren") [zutat] = pruefe_zutaten(db, [_zutat("Safranfäden", 1, "g")]) assert zutat.status == "kein_treffer" assert kochbar([zutat]) is False def test_zutat_ohne_menge_ist_unklar(db): cat = _kategorie(db, "Gewürze") _artikel(db, "Salz", cat, bestand=500) [zutat] = pruefe_zutaten(db, [_zutat("Salz", None, None, text="Salz nach Geschmack")]) assert zutat.status == "menge_unbekannt" assert kochbar([zutat]) is False def test_ohne_menge_und_ohne_bestand_fehlt_es(db): cat = _kategorie(db, "Gewürze") _artikel(db, "Salz", cat, bestand=0) [zutat] = pruefe_zutaten(db, [_zutat("Salz", None, None)]) assert zutat.status == "fehlt" # -------------------------------------------------------------------------- # Zuordnung # -------------------------------------------------------------------------- def test_aehnlicher_name_wird_gefunden(db): cat = _kategorie(db, "Trockenwaren") _artikel(db, "Weissmehl Typ 550", cat, bestand=2000) [zutat] = pruefe_zutaten(db, [_zutat("Weissmehl", 200, "g")]) assert zutat.art == "product" assert zutat.ziel_name == "Weissmehl Typ 550" def test_gruppe_zaehlt_marken_zusammen(db): """„Milch" ist markenuebergreifend gemeint – dafuer gibt es Gruppen.""" gruppe = Group(name="Milch") db.add(gruppe) db.commit() cat = _kategorie(db, "Molkerei") a = _artikel(db, "Valflora Vollmilch", cat, base=BaseUnit.milliliter, bestand=1000) b = _artikel(db, "Emmi Vollmilch", cat, base=BaseUnit.milliliter, bestand=500) a.group_id = gruppe.id b.group_id = gruppe.id db.commit() [zutat] = pruefe_zutaten(db, [_zutat("Milch", 1200, "ml")]) assert zutat.art == "group" assert zutat.bestand_base == 1500 # beide Marken zusammen assert zutat.status == "vorhanden" def test_gemerkte_zuordnung_schlaegt_die_suche(db): cat = _kategorie(db, "Trockenwaren") _artikel(db, "Weissmehl", cat, bestand=1000) richtig = _artikel(db, "Dinkelmehl", cat, bestand=2000) [zutat] = pruefe_zutaten( db, [_zutat("Mehl", 100, "g")], zuordnung={"mehl": ("product", richtig.id)}, ) assert zutat.ziel_id == richtig.id assert zutat.ziel_name == "Dinkelmehl" def test_voellig_fremde_zutat_liefert_gar_keinen_vorschlag(db): """Lieber nichts als Unsinn. Fruehere Fassung schlug hier „Weissmehl" vor, weil sich Buchstaben ueberlappen. Ein Vorschlag, den niemand nachvollziehen kann, ist schlimmer als keiner – er verleitet zum Durchklicken. """ cat = _kategorie(db, "Trockenwaren") _artikel(db, "Weissmehl", cat, bestand=1000) [zutat] = pruefe_zutaten(db, [_zutat("Kurkuma", 1, "g")]) assert zutat.status == "kein_treffer" assert zutat.ziel_id is None assert zutat.kandidaten == [] # -------------------------------------------------------------------------- # Was NICHT passen darf – die Faelle aus dem echten Katalog # -------------------------------------------------------------------------- def test_gegenstaende_kommen_nicht_in_frage(db): """Ein Joystick ist kein Zucker und eine Waffelplatte kein Apfel.""" essen = _kategorie(db, "Trockenwaren") _artikel(db, "Weissmehl", essen, bestand=1000) kram = Category(name="Elektronik", tracking=CategoryTracking.object.value) db.add(kram) db.commit() db.refresh(kram) for name in ("Joystick für WII", "Snack Collection Platten 4 (Waffeln)", "Shirt Casual Fit"): db.add(Product(name=name, base_unit=BaseUnit.piece, category_id=kram.id, source="manual")) db.commit() for zutat_name in ("Zucker", "Apfel", "Salz"): [zutat] = pruefe_zutaten(db, [_zutat(zutat_name, 1, "g")]) assert zutat.status == "kein_treffer", zutat_name assert all("Joystick" not in k.name for k in zutat.kandidaten) def test_mehl_trifft_nicht_das_geschnetzelte(db): """Buchstaben-Aehnlichkeit allein reicht nicht – es braucht ein ganzes Wort.""" fleisch = _kategorie(db, "Fleisch & Fisch") _artikel(db, "Mühlen Geschnetzeltes", fleisch, bestand=500) [zutat] = pruefe_zutaten(db, [_zutat("Mehl Typ 405", 125, "g")]) assert zutat.status == "kein_treffer" def test_eier_treffen_nicht_den_reis(db): cat = _kategorie(db, "Trockenwaren") _artikel(db, "Reis", cat, bestand=2000) [zutat] = pruefe_zutaten(db, [_zutat("Eier", 3, None)]) assert zutat.status == "kein_treffer" def test_kategorie_wird_zuerst_durchsucht(db): """Steht die Zutat in einer passenden Kategorie, gilt nur die.""" fleisch = _kategorie(db, "Fleisch & Fisch") _artikel(db, "Mühlen Geschnetzeltes", fleisch, bestand=500) backen = _kategorie(db, "Mehl & Zucker") richtig = _artikel(db, "Weissmehl Typ 405", backen, bestand=2000) [zutat] = pruefe_zutaten(db, [_zutat("Mehl Typ 405", 125, "g")]) assert zutat.ziel_id == richtig.id assert zutat.status == "vorhanden" def test_umlaut_und_mehrzahl_finden_sich(db): """Rezept schreibt „Apfel", im Katalog stehen „Äpfel".""" cat = _kategorie(db, "Obst & Gemüse") aepfel = _artikel(db, "Äpfel", cat, base=BaseUnit.gram, bestand=2000) [zutat] = pruefe_zutaten(db, [_zutat("Apfel", 900, "g")]) assert zutat.ziel_id == aepfel.id assert zutat.status == "vorhanden" def test_vollmilch_findet_die_h_vollmilch(db): cat = _kategorie(db, "Molkerei") milch = _artikel(db, "H Vollmilch", cat, base=BaseUnit.milliliter, bestand=1000) [zutat] = pruefe_zutaten(db, [_zutat("Vollmilch Zimmertemperatur", 120, "ml")]) assert zutat.ziel_id == milch.id assert zutat.status == "vorhanden" # -------------------------------------------------------------------------- # Hausmaße # -------------------------------------------------------------------------- def test_essloeffel_wird_umgerechnet(db): cat = _kategorie(db, "Mehl") mehl = _artikel(db, "Weissmehl", cat, bestand=1000) el = db.query(Measure).filter(Measure.name == "Esslöffel").one() db.add(CategoryMeasure(measure_id=el.id, category_id=cat.id, amount=10, base=BaseUnit.gram.value)) db.commit() [zutat] = pruefe_zutaten(db, [_zutat("Weissmehl", 2, "EL")]) assert zutat.benoetigt_base == 20 assert zutat.status == "vorhanden" def test_fehlendes_mass_wird_zum_anlegen_gemeldet(db): """Daraus baut die Oberflaeche „Maß jetzt anlegen".""" cat = _kategorie(db, "Hülsenfrüchte") _artikel(db, "Berglinsen", cat, bestand=1000) [zutat] = pruefe_zutaten(db, [_zutat("Berglinsen", 1, "Tasse")]) assert zutat.status == "menge_unbekannt" assert zutat.fehlendes_mass == "Tasse" assert zutat.kategorie_id == cat.id assert "kein Maß" in zutat.hinweis def test_unbekannte_einheit_wird_benannt(db): cat = _kategorie(db, "Trockenwaren") _artikel(db, "Weissmehl", cat, bestand=1000) [zutat] = pruefe_zutaten(db, [_zutat("Weissmehl", 1, "furlong")]) assert zutat.status == "menge_unbekannt" assert "furlong" in zutat.hinweis # -------------------------------------------------------------------------- # Ganzes Rezept # -------------------------------------------------------------------------- def test_ein_rezept_mit_allem(db): cat = _kategorie(db, "Trockenwaren") _artikel(db, "Weissmehl", cat, bestand=1000) _artikel(db, "Zucker", cat, bestand=50) zutaten = [ _zutat("Weissmehl", 200, "g"), # reicht _zutat("Zucker", 100, "g"), # zu wenig _zutat("Safran", 1, "g"), # kennt niemand ] ergebnis = pruefe_zutaten(db, zutaten) assert [z.status for z in ergebnis] == ["vorhanden", "zu_wenig", "kein_treffer"] assert kochbar(ergebnis) is False