from datetime import date, datetime, timezone from decimal import Decimal from uuid import uuid4 import pytest pytestmark = pytest.mark.requires_postgres def _admin() -> dict: return {"Authorization": "Bearer test-admin-token"} @pytest.fixture def family_with_groceries(db_session): from app.models import FamilyProfile, GroceryItem, Ingredient family = FamilyProfile( id=uuid4(), name="API Smoke Family", household_size=4, adult_count=2, child_count=2, calorie_target=500, # per-serving target; recipes are 360-640 range ) db_session.add(family) db_session.commit() for ing in db_session.query(Ingredient).limit(20).all(): db_session.add( GroceryItem( id=uuid4(), name=ing.name, source="lucky_california", external_id=f"ext-api-{ing.id}", current_price=Decimal("3.99"), regular_price=Decimal("4.99"), is_on_sale=True, scraped_at=datetime.now(timezone.utc), ) ) db_session.commit() from app.services.matcher import run_match_job run_match_job(db_session) return family def test_generate_endpoint_returns_meal_plan(client, family_with_groceries): body = { "family_profile_id": str(family_with_groceries.id), "week_start_date": "2026-05-11", } r = client.post("/api/admin/meal-plans/generate", json=body, headers=_admin()) assert r.status_code == 201, r.text data = r.json() assert "meal_plan_id" in data assert 1 <= len(data["items"]) <= 3 assert "debug" in data assert data["debug"]["feasible_count"] >= 1 def test_generate_endpoint_requires_admin_token(client, family_with_groceries): body = { "family_profile_id": str(family_with_groceries.id), "week_start_date": "2026-05-18", } r = client.post("/api/admin/meal-plans/generate", json=body) assert r.status_code == 401 def test_public_generate_uses_session_family_and_three_dinner_schedule( client, family_with_groceries ): from app.security import SESSION_COOKIE, issue_session client.cookies.set( SESSION_COOKIE, issue_session(str(family_with_groceries.id)), ) r = client.post( "/api/meal-plans/generate", json={"week_start_date": "2026-06-01"}, ) assert r.status_code == 201, r.text data = r.json() assert [item["day_of_week"] for item in data["items"]] == [1, 3, 5] def test_public_generate_requires_session(client): r = client.post( "/api/meal-plans/generate", json={"week_start_date": "2026-06-08"}, ) assert r.status_code == 401 def test_public_generate_preserves_populated_plan(client, db, family_with_groceries): from app.models import MealPlan, MealPlanItem from app.security import SESSION_COOKIE, issue_session client.cookies.set( SESSION_COOKIE, issue_session(str(family_with_groceries.id)), ) payload = {"week_start_date": "2026-06-15"} first = client.post("/api/meal-plans/generate", json=payload) assert first.status_code == 201, first.text plan_id = first.json()["meal_plan_id"] item_ids = { item_id for (item_id,) in db.query(MealPlanItem.id).filter( MealPlanItem.meal_plan_id == plan_id ).all() } second = client.post("/api/meal-plans/generate", json=payload) assert second.status_code == 409 assert db.query(MealPlan).filter(MealPlan.id == plan_id).first() is not None assert { item_id for (item_id,) in db.query(MealPlanItem.id).filter( MealPlanItem.meal_plan_id == plan_id ).all() } == item_ids def test_public_generate_replaces_only_empty_plan(client, db, family_with_groceries): from app.models import MealPlan from app.security import SESSION_COOKIE, issue_session empty_plan = MealPlan( family_profile_id=family_with_groceries.id, week_start_date=date(2026, 6, 22), status="draft", ) db.add(empty_plan) db.flush() empty_plan_id = empty_plan.id client.cookies.set( SESSION_COOKIE, issue_session(str(family_with_groceries.id)), ) response = client.post( "/api/meal-plans/generate", json={"week_start_date": "2026-06-22"}, ) assert response.status_code == 201, response.text assert response.json()["meal_plan_id"] != str(empty_plan_id) assert db.query(MealPlan).filter(MealPlan.id == empty_plan_id).first() is None def test_generate_endpoint_returns_404_for_unknown_family(client): body = { "family_profile_id": str(uuid4()), "week_start_date": "2026-05-11", } r = client.post("/api/admin/meal-plans/generate", json=body, headers=_admin()) assert r.status_code == 404