import asyncio import json from pathlib import Path from types import SimpleNamespace import pytest from db import connect, create from recipes import parse_recipe_from_html SNAP_DIR = Path("tests/sample_files/recipes") def _iter_expected_files(): return sorted(SNAP_DIR.glob("*.recipe.json")) def _load_expected(path: Path): # New-style file contains the model JSON directly or null return json.loads(path.read_text()) def _expected_subset(expected: dict): # expected is serialized with by_alias=True, so image_urls is imageUrls name = expected.get("name") link = expected.get("link") serves = expected.get("serves") image_urls = expected.get("imageUrls") or [] ingredients = expected.get("ingredients") or [] ing_lines = [ing.get("line") for ing in ingredients] return { "name": name, "link": link, "serves": serves, "image_urls": image_urls, "ingredient_lines": ing_lines, } def _actual_subset(model): # Stable, assertion-friendly projection return { "name": model.name, "link": model.link, "serves": model.serves, "image_urls": list(model.image_urls or []), "ingredient_lines": [ing.line for ing in (model.ingredients or [])], } @pytest.mark.parametrize("exp_path", _iter_expected_files()) def test_offline_parse_matches_expected(exp_path: Path): async def _run(): slug = exp_path.stem expected = _load_expected(exp_path) html_path = SNAP_DIR / f"{slug}.html" assert html_path.exists(), f"snapshot HTML missing for {slug}" html = html_path.read_text() # Discover base URL from meta if available meta_path = SNAP_DIR / f"{slug}.meta.json" base_url = slug if meta_path.exists(): try: meta = json.loads(meta_path.read_text()) base_url = meta.get("final_url") or meta.get("url") or slug except Exception: pass created_by = SimpleNamespace(id=1, display_name="Snapshot Generator") conn = await connect() await create(conn) try: model = await parse_recipe_from_html(conn, created_by, base_url, html) finally: await conn.close() if expected is None: assert model is None, f"expected no parse for {slug}, but got a model" return assert model is not None, f"expected a model for {slug}, got None" exp_sub = _expected_subset(expected) act_sub = _actual_subset(model) # Compare key fields; ingredients compare by line only for stability assert act_sub == exp_sub asyncio.run(_run())