kiwi/tests/api/test_meal_plans.py

116 lines
3.6 KiB
Python

# tests/api/test_meal_plans.py
"""Integration tests for /api/v1/meal-plans/ endpoints."""
from __future__ import annotations
import json
from unittest.mock import MagicMock
import pytest
from fastapi.testclient import TestClient
from app.cloud_session import get_session
from app.db.session import get_store
from app.main import app
client = TestClient(app)
def _make_session(tier: str = "free") -> MagicMock:
m = MagicMock()
m.tier = tier
m.has_byok = False
return m
def _make_store() -> MagicMock:
m = MagicMock()
m.create_meal_plan.return_value = {
"id": 1, "week_start": "2026-04-14",
"meal_types": ["dinner"], "created_at": "2026-04-12T10:00:00",
}
m.list_meal_plans.return_value = []
m.get_meal_plan.return_value = None
m.get_plan_slots.return_value = []
m.upsert_slot.return_value = {
"id": 1, "plan_id": 1, "day_of_week": 0, "meal_type": "dinner",
"recipe_id": 42, "recipe_title": "Pasta", "servings": 2.0, "custom_label": None,
}
m.get_inventory.return_value = []
m.get_plan_recipes.return_value = []
m.get_prep_session_for_plan.return_value = None
m.create_prep_session.return_value = {
"id": 1, "plan_id": 1, "scheduled_date": "2026-04-13",
"status": "draft", "created_at": "2026-04-12T10:00:00",
}
m.get_prep_tasks.return_value = []
m.bulk_insert_prep_tasks.return_value = []
return m
@pytest.fixture()
def free_session():
session = _make_session("free")
store = _make_store()
app.dependency_overrides[get_session] = lambda: session
app.dependency_overrides[get_store] = lambda: store
yield store
app.dependency_overrides.clear()
@pytest.fixture()
def paid_session():
session = _make_session("paid")
store = _make_store()
app.dependency_overrides[get_session] = lambda: session
app.dependency_overrides[get_store] = lambda: store
yield store
app.dependency_overrides.clear()
def test_create_plan_free_tier_locks_to_dinner(free_session):
resp = client.post("/api/v1/meal-plans/", json={
"week_start": "2026-04-14", "meal_types": ["breakfast", "dinner"]
})
assert resp.status_code == 200
# Free tier forced to dinner-only regardless of request
free_session.create_meal_plan.assert_called_once_with("2026-04-14", ["dinner"])
def test_create_plan_paid_tier_respects_meal_types(paid_session):
resp = client.post("/api/v1/meal-plans/", json={
"week_start": "2026-04-14", "meal_types": ["breakfast", "lunch", "dinner"]
})
assert resp.status_code == 200
paid_session.create_meal_plan.assert_called_once_with(
"2026-04-14", ["breakfast", "lunch", "dinner"]
)
def test_list_plans_returns_200(free_session):
resp = client.get("/api/v1/meal-plans/")
assert resp.status_code == 200
assert resp.json() == []
def test_upsert_slot_returns_200(free_session):
free_session.get_meal_plan.return_value = {
"id": 1, "week_start": "2026-04-14", "meal_types": ["dinner"],
"created_at": "2026-04-12T10:00:00",
}
resp = client.put(
"/api/v1/meal-plans/1/slots/0/dinner",
json={"recipe_id": 42, "servings": 2.0},
)
assert resp.status_code == 200
def test_get_shopping_list_returns_200(free_session):
free_session.get_meal_plan.return_value = {
"id": 1, "week_start": "2026-04-14", "meal_types": ["dinner"],
"created_at": "2026-04-12T10:00:00",
}
resp = client.get("/api/v1/meal-plans/1/shopping-list")
assert resp.status_code == 200
body = resp.json()
assert "gap_items" in body
assert "covered_items" in body