munch-ease-backend/tests/test_recipes_parse_from_url_integration_v2.py

120 lines
4.2 KiB
Python
Raw Normal View History

2025-11-01 12:05:16 +00:00
import unittest
from fastapi.testclient import TestClient
import main
from db import connect, create
from scripts.migration_to_households import run_migration
SAMPLE_URL = "https://www.allrecipes.com/recipe/262696/cheese-omelette/"
with open("tests/sample_files/recipes/cheese-omelette.html", "r", encoding="utf-8") as f:
SAMPLE_HTML = f.read()
class TestRecipesParseFromUrlIntegrationV2(unittest.IsolatedAsyncioTestCase):
async def asyncSetUp(self):
self.conn = await connect(":memory:")
await create(self.conn)
await run_migration(self.conn)
async def override_get_db():
try:
yield self.conn
finally:
pass
main.app.dependency_overrides[main.get_db] = override_get_db
self.client = TestClient(main.app)
# Register user and create household
r = self.client.post(
"/api/v1/auth/register",
json={"email": "scrape@test.com", "password": "pw", "displayName": "Scrape"},
)
assert r.status_code == 200, r.text
token = r.json()["accessToken"]
self.headers = {"Authorization": f"Bearer {token}"}
r2 = self.client.post("/api/v1/households", headers=self.headers, json={"name": "H"})
assert r2.status_code == 200, r2.text
self.slug = r2.json()["slug"]
async def asyncTearDown(self):
await self.conn.close()
main.app.dependency_overrides.clear()
def test_parse_from_url_with_mocked_html(self):
# Monkeypatch httpx.AsyncClient.get used in scraper
import recipes.scraping as scraping
class DummyResp:
def __init__(self, status_code=200, text=""):
self.status_code = status_code
self.text = text
class DummyClient:
async def __aenter__(self):
return self
async def __aexit__(self, exc_type, exc, tb):
return False
async def get(self, url, headers=None, follow_redirects=False):
assert url == SAMPLE_URL
return DummyResp(200, SAMPLE_HTML)
orig_client = scraping.httpx.AsyncClient
scraping.httpx.AsyncClient = DummyClient
try:
r = self.client.post(
f"/api/v1/households/{self.slug}/recipes/parse-from-url",
headers=self.headers,
json={"url": SAMPLE_URL},
)
assert r.status_code == 200, r.text
data = r.json()
assert data.get("@type") in ("Recipe", ["Recipe"]) # accept single or list
# JSON-LD should contain ingredients and instructions
assert isinstance(data.get("recipeIngredient"), list)
assert len(data["recipeIngredient"]) >= 1
# The title should mention omelette
assert "name" in data and "omelette" in data["name"].lower()
# Optional: if author or image present, assert types
if "image" in data:
assert isinstance(data["image"], (str, list, dict))
if "author" in data:
assert isinstance(data["author"], (str, list, dict))
finally:
scraping.httpx.AsyncClient = orig_client
def test_parse_from_url_not_found(self):
import recipes.scraping as scraping
class DummyResp:
def __init__(self, status_code=404, text=""):
self.status_code = status_code
self.text = text
class DummyClient:
async def __aenter__(self):
return self
async def __aexit__(self, exc_type, exc, tb):
return False
async def get(self, url, headers=None, follow_redirects=False):
return DummyResp(404, "")
orig_client = scraping.httpx.AsyncClient
scraping.httpx.AsyncClient = DummyClient
try:
r = self.client.post(
f"/api/v1/households/{self.slug}/recipes/parse-from-url",
headers=self.headers,
json={"url": SAMPLE_URL},
)
assert r.status_code == 404, r.text
body = r.json()
assert body.get("status") == 404
finally:
scraping.httpx.AsyncClient = orig_client