93 lines
2.7 KiB
Python
93 lines
2.7 KiB
Python
|
|
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())
|