Public Access
feat(meals): enforce three lower-calorie dinners with media
This commit is contained in:
@@ -11,6 +11,37 @@ pytestmark = pytest.mark.requires_postgres
|
||||
WEEK = date(2026, 5, 8) # a Friday
|
||||
|
||||
|
||||
def _add_quarantined_item(db, meal_plan, *, name, approval_status, day_of_week):
|
||||
from app.models import (
|
||||
MealPlanItem,
|
||||
MealType,
|
||||
Recipe,
|
||||
RecipeCatalogStatus,
|
||||
)
|
||||
|
||||
recipe = Recipe(
|
||||
id=uuid4(),
|
||||
name=name,
|
||||
servings=4,
|
||||
ingredients=[{"name": "legacy ingredient", "qty": "1", "unit": "cup"}],
|
||||
instructions=["Do not include this legacy recipe."],
|
||||
catalog_status=RecipeCatalogStatus.QUARANTINED,
|
||||
)
|
||||
db.add(recipe)
|
||||
db.flush()
|
||||
item = MealPlanItem(
|
||||
id=uuid4(),
|
||||
meal_plan_id=meal_plan.id,
|
||||
recipe_id=recipe.id,
|
||||
day_of_week=day_of_week,
|
||||
meal_type=MealType.DINNER,
|
||||
approval_status=approval_status,
|
||||
)
|
||||
db.add(item)
|
||||
db.flush()
|
||||
return item
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def family(db):
|
||||
from app.models import FamilyProfile
|
||||
@@ -339,6 +370,38 @@ def test_step_email_escapes_recipe_name(
|
||||
assert "<script>" in sent[0]["html"]
|
||||
|
||||
|
||||
def test_step_email_suppresses_quarantined_recipes(
|
||||
db, weekly_run_generated, meal_plan, pending_item, member, monkeypatch
|
||||
):
|
||||
from app.models import MealPlanItemStatus
|
||||
|
||||
_add_quarantined_item(
|
||||
db,
|
||||
meal_plan,
|
||||
name="Legacy Mango Pork",
|
||||
approval_status=MealPlanItemStatus.pending,
|
||||
day_of_week=6,
|
||||
)
|
||||
sent = []
|
||||
|
||||
class FakeBackend:
|
||||
def send(self, **kwargs):
|
||||
sent.append(kwargs)
|
||||
|
||||
monkeypatch.setattr(
|
||||
"app.services.orchestrator.steps.get_email_backend",
|
||||
lambda: FakeBackend(),
|
||||
)
|
||||
from app.services.orchestrator.steps import step_email
|
||||
|
||||
step_email(weekly_run_generated, db)
|
||||
|
||||
assert len(sent) == 1
|
||||
assert "Test Pasta" in sent[0]["html"]
|
||||
assert "Legacy Mango Pork" not in sent[0]["html"]
|
||||
assert "legacy ingredient" not in sent[0]["html"]
|
||||
|
||||
|
||||
# ── step_reminder ──────────────────────────────────────────────────────────
|
||||
|
||||
|
||||
@@ -447,6 +510,37 @@ def test_step_reminder_no_sends_when_all_voted(
|
||||
assert weekly_run_emailed.reminded_at is not None
|
||||
|
||||
|
||||
def test_step_reminder_suppresses_quarantined_recipes(
|
||||
db, weekly_run_emailed, meal_plan, pending_item, member, monkeypatch
|
||||
):
|
||||
from app.models import MealPlanItemStatus
|
||||
|
||||
_add_quarantined_item(
|
||||
db,
|
||||
meal_plan,
|
||||
name="Legacy Mango Pork",
|
||||
approval_status=MealPlanItemStatus.pending,
|
||||
day_of_week=6,
|
||||
)
|
||||
sent = []
|
||||
|
||||
class FakeBackend:
|
||||
def send(self, **kwargs):
|
||||
sent.append(kwargs)
|
||||
|
||||
monkeypatch.setattr(
|
||||
"app.services.orchestrator.steps.get_email_backend",
|
||||
lambda: FakeBackend(),
|
||||
)
|
||||
from app.services.orchestrator.steps import step_reminder
|
||||
|
||||
step_reminder(weekly_run_emailed, db)
|
||||
|
||||
assert len(sent) == 1
|
||||
assert "Test Pasta" in sent[0]["html"]
|
||||
assert "Legacy Mango Pork" not in sent[0]["html"]
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# step_deadline tests
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -573,6 +667,158 @@ def test_step_finalize_no_approved_sends_empty_message(db, weekly_run_deadline_p
|
||||
assert "No meals were approved" in sent[0]["html"]
|
||||
|
||||
|
||||
def test_step_finalize_suppresses_quarantined_recipes(
|
||||
db, weekly_run_deadline_passed, meal_plan, approved_item, member, monkeypatch
|
||||
):
|
||||
from app.models import MealPlanItemStatus
|
||||
|
||||
_add_quarantined_item(
|
||||
db,
|
||||
meal_plan,
|
||||
name="Legacy Mango Pork",
|
||||
approval_status=MealPlanItemStatus.approved,
|
||||
day_of_week=7,
|
||||
)
|
||||
sent = []
|
||||
|
||||
class FakeBackend:
|
||||
def send(self, **kwargs):
|
||||
sent.append(kwargs)
|
||||
|
||||
monkeypatch.setattr(
|
||||
"app.services.orchestrator.steps.get_email_backend",
|
||||
lambda: FakeBackend(),
|
||||
)
|
||||
from app.services.orchestrator.steps import step_finalize
|
||||
|
||||
step_finalize(weekly_run_deadline_passed, db)
|
||||
|
||||
assert len(sent) == 1
|
||||
assert "Approved Chicken" in sent[0]["html"]
|
||||
assert "Legacy Mango Pork" not in sent[0]["html"]
|
||||
assert "legacy ingredient" not in sent[0]["html"]
|
||||
|
||||
|
||||
def test_step_finalize_stages_candidates_without_publishing(
|
||||
db, weekly_run_deadline_passed, meal_plan, member, monkeypatch
|
||||
):
|
||||
from types import SimpleNamespace
|
||||
from app.models import Recipe, RecipeImportStaging, RecipeImportStatus
|
||||
|
||||
class FakeBackend:
|
||||
def send(self, **kwargs):
|
||||
pass
|
||||
|
||||
analysis = SimpleNamespace(discovery_queries=["apple dinner"], confidence=0.9)
|
||||
analysis.to_dict = lambda: {
|
||||
"discovery_queries": analysis.discovery_queries,
|
||||
"confidence": analysis.confidence,
|
||||
}
|
||||
monkeypatch.setattr(
|
||||
"app.services.orchestrator.steps.get_email_backend", lambda: FakeBackend()
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
"app.services.orchestrator.steps.FeedbackAnalyzer.analyze",
|
||||
lambda self, database, family_id: analysis,
|
||||
)
|
||||
external_id = str(uuid4())
|
||||
|
||||
def stage(service, database, limit, queries):
|
||||
assert queries == ["apple dinner"]
|
||||
row = RecipeImportStaging(
|
||||
source="wikibooks",
|
||||
external_id=external_id,
|
||||
source_url="https://en.wikibooks.org/wiki/Cookbook:Finalize_Test",
|
||||
source_revision=str(uuid4()),
|
||||
license_name="CC BY-SA 4.0",
|
||||
license_url="https://creativecommons.org/licenses/by-sa/4.0/",
|
||||
attribution="Wikibooks contributors",
|
||||
raw_payload={"title": "Cookbook:Finalize Test"},
|
||||
payload_sha256="a" * 64,
|
||||
normalized_recipe={"name": "Finalize Test"},
|
||||
validation_report={"valid": True, "errors": [], "warnings": []},
|
||||
status=RecipeImportStatus.PENDING,
|
||||
)
|
||||
database.add(row)
|
||||
database.flush()
|
||||
return [row]
|
||||
|
||||
monkeypatch.setattr(
|
||||
"app.services.orchestrator.steps.WikibooksImportService.stage_discovered_pages",
|
||||
stage,
|
||||
)
|
||||
before = db.query(Recipe).count()
|
||||
|
||||
from app.services.orchestrator.steps import step_finalize
|
||||
step_finalize(weekly_run_deadline_passed, db)
|
||||
|
||||
assert weekly_run_deadline_passed.status == "completed"
|
||||
assert (
|
||||
db.query(RecipeImportStaging).filter_by(external_id=external_id).count() == 1
|
||||
)
|
||||
assert db.query(Recipe).count() == before
|
||||
|
||||
|
||||
def test_step_finalize_rolls_back_partial_staging_failure(
|
||||
db, weekly_run_deadline_passed, meal_plan, member, monkeypatch
|
||||
):
|
||||
from types import SimpleNamespace
|
||||
from app.models import RecipeImportStaging, RecipeImportStatus
|
||||
|
||||
class FakeBackend:
|
||||
def send(self, **kwargs):
|
||||
pass
|
||||
|
||||
analysis = SimpleNamespace(discovery_queries=["apple dinner"], confidence=0.9)
|
||||
analysis.to_dict = lambda: {
|
||||
"discovery_queries": analysis.discovery_queries,
|
||||
"confidence": analysis.confidence,
|
||||
}
|
||||
monkeypatch.setattr(
|
||||
"app.services.orchestrator.steps.get_email_backend", lambda: FakeBackend()
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
"app.services.orchestrator.steps.FeedbackAnalyzer.analyze",
|
||||
lambda self, database, family_id: analysis,
|
||||
)
|
||||
|
||||
external_id = str(uuid4())
|
||||
|
||||
def stage_then_fail(service, database, limit, queries):
|
||||
assert queries == ["apple dinner"]
|
||||
database.add(
|
||||
RecipeImportStaging(
|
||||
source="wikibooks",
|
||||
external_id=external_id,
|
||||
source_url="https://en.wikibooks.org/wiki/Cookbook:Partial_Test",
|
||||
source_revision=str(uuid4()),
|
||||
license_name="CC BY-SA 4.0",
|
||||
license_url="https://creativecommons.org/licenses/by-sa/4.0/",
|
||||
attribution="Wikibooks contributors",
|
||||
raw_payload={"title": "Cookbook:Partial Test"},
|
||||
payload_sha256="b" * 64,
|
||||
normalized_recipe={"name": "Partial Test"},
|
||||
validation_report={"valid": True, "errors": [], "warnings": []},
|
||||
status=RecipeImportStatus.PENDING,
|
||||
)
|
||||
)
|
||||
database.flush()
|
||||
raise RuntimeError("upstream failed after first page")
|
||||
|
||||
monkeypatch.setattr(
|
||||
"app.services.orchestrator.steps.WikibooksImportService.stage_discovered_pages",
|
||||
stage_then_fail,
|
||||
)
|
||||
|
||||
from app.services.orchestrator.steps import step_finalize
|
||||
step_finalize(weekly_run_deadline_passed, db)
|
||||
|
||||
assert weekly_run_deadline_passed.status == "completed"
|
||||
assert (
|
||||
db.query(RecipeImportStaging).filter_by(external_id=external_id).count() == 0
|
||||
)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# runner tests
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user