diff --git a/backend/app/services/orchestrator/steps.py b/backend/app/services/orchestrator/steps.py index 11d61b7..b936c60 100644 --- a/backend/app/services/orchestrator/steps.py +++ b/backend/app/services/orchestrator/steps.py @@ -132,7 +132,9 @@ def step_email(run: "WeeklyRun", db: "Session") -> None: vote_url = ( f"{settings.APP_BASE_URL}/api/meals/vote/{item.id}?token={token}" ) - recipe_name = item.recipe.name if item.recipe else str(item.recipe_id) + recipe_name = html.escape( + item.recipe.name if item.recipe else str(item.recipe_id) + ) item_html_parts.append( f'
Hi {member.name}, please vote on this week's meals by Fri 17:00 PT:
" @@ -150,7 +152,7 @@ def step_email(run: "WeeklyRun", db: "Session") -> None: backend.send( to=member.email, subject=f"Meal plan for week of {run.week_start_date}", - html=html, + html=email_html, ) logger.info("step_email: sent to %s", member.email) @@ -177,7 +179,7 @@ def step_deadline(run: "WeeklyRun", db: "Session") -> None: return family = db.query(FamilyProfile).filter(FamilyProfile.id == run.family_id).first() - policy = getattr(family, "pending_approval_policy", "approve") if family else "approve" + policy = family.pending_approval_policy if family else "approve" resolved = 0 for item in plan.items: @@ -227,28 +229,28 @@ def step_finalize(run: "WeeklyRun", db: "Session") -> None: if approved_items: rows_html = "".join( - f"{len(approved_items)} meal(s) approved.
" f"| Recipe | Ingredient | Qty |
|---|
No meals were approved for week of {run.week_start_date}.
" + email_html = f"No meals were approved for week of {run.week_start_date}.
" backend = get_email_backend() for member in members: backend.send( to=member.email, subject=f"Shopping list — week of {run.week_start_date}", - html=html, + html=email_html, ) logger.info("step_finalize: shopping list sent to %s", member.email) diff --git a/backend/tests/test_orchestrator.py b/backend/tests/test_orchestrator.py index d4a8c59..08fff3f 100644 --- a/backend/tests/test_orchestrator.py +++ b/backend/tests/test_orchestrator.py @@ -316,6 +316,29 @@ def test_step_email_stale_banner(db, weekly_run_generated, meal_plan, pending_it assert "prices" in sent[0]["html"].lower() or "stale" in sent[0]["html"].lower() +def test_step_email_escapes_recipe_name( + db, weekly_run_generated, meal_plan, pending_item, member, monkeypatch +): + pending_item.recipe.name = "" + db.flush() + + 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 "