sync - build workin

This commit is contained in:
2025-09-29 09:31:01 -07:00
parent 81f24ec0e7
commit 989576e7b2
7 changed files with 40 additions and 39 deletions

193
tests/test_meals.py Normal file
View File

@@ -0,0 +1,193 @@
"""
Tests for Meals CRUD operations
"""
import pytest
import json
class TestMealsRoutes:
"""Test meal-related routes"""
def test_get_meals_page(self, client):
"""Test GET /meals page"""
response = client.get("/meals")
assert response.status_code == 200
assert b"Meals" in response.content or b"meals" in response.content
def test_add_meal(self, client):
"""Test POST /meals/add"""
response = client.post("/meals/add", data={
"name": "New Test Meal",
"meal_type": "lunch",
"meal_time": "Lunch"
})
assert response.status_code == 200
data = response.json()
assert data["status"] == "success"
assert "meal_id" in data
def test_edit_meal(self, client, sample_meal):
"""Test POST /meals/edit"""
response = client.post("/meals/edit", data={
"meal_id": sample_meal.id,
"name": "Updated Meal Name",
"meal_type": "dinner",
"meal_time": "Dinner"
})
assert response.status_code == 200
data = response.json()
assert data["status"] == "success"
def test_edit_nonexistent_meal(self, client):
"""Test editing non-existent meal"""
response = client.post("/meals/edit", data={
"meal_id": 99999,
"name": "Updated Meal Name",
"meal_type": "dinner",
"meal_time": "Dinner"
})
assert response.status_code == 200
data = response.json()
assert data["status"] == "error"
def test_get_meal_details(self, client, sample_meal):
"""Test GET /meals/{meal_id}"""
response = client.get(f"/meals/{sample_meal.id}")
assert response.status_code == 200
data = response.json()
assert data["status"] == "success"
assert data["id"] == sample_meal.id
assert data["name"] == sample_meal.name
def test_get_nonexistent_meal_details(self, client):
"""Test getting details for non-existent meal"""
response = client.get("/meals/99999")
assert response.status_code == 200
data = response.json()
assert data["status"] == "error"
def test_delete_meals(self, client, sample_meal):
"""Test POST /meals/delete"""
response = client.post("/meals/delete",
json={"meal_ids": [sample_meal.id]})
assert response.status_code == 200
data = response.json()
assert data["status"] == "success"
class TestMealFoods:
"""Test meal-food relationships"""
def test_get_meal_foods(self, client, sample_meal):
"""Test GET /meals/{meal_id}/foods"""
response = client.get(f"/meals/{sample_meal.id}/foods")
assert response.status_code == 200
data = response.json()
assert isinstance(data, list)
if len(data) > 0:
assert "food_id" in data[0]
assert "quantity" in data[0]
def test_add_food_to_meal(self, client, sample_meal, sample_food):
"""Test POST /meals/{meal_id}/add_food"""
response = client.post(f"/meals/{sample_meal.id}/add_food", data={
"food_id": sample_food.id,
"quantity": 2.5
})
assert response.status_code == 200
data = response.json()
assert data["status"] == "success"
def test_remove_food_from_meal(self, client, sample_meal, db_session):
"""Test DELETE /meals/remove_food/{meal_food_id}"""
# Get the first meal food
from main import MealFood
meal_food = db_session.query(MealFood).filter(
MealFood.meal_id == sample_meal.id
).first()
if meal_food:
response = client.delete(f"/meals/remove_food/{meal_food.id}")
assert response.status_code == 200
data = response.json()
assert data["status"] == "success"
def test_remove_nonexistent_meal_food(self, client):
"""Test removing non-existent meal food"""
response = client.delete("/meals/remove_food/99999")
assert response.status_code == 200
data = response.json()
assert data["status"] == "error"
class TestMealsBulkUpload:
"""Test bulk meal upload functionality"""
def test_bulk_upload_meals_csv(self, client, sample_foods, tmp_path):
"""Test POST /meals/upload with CSV"""
# Create test CSV file with meal recipes
csv_content = f"""Meal Name,Food 1,Grams 1,Food 2,Grams 2
Test Meal 1,{sample_foods[0].name},150,{sample_foods[1].name},200
Test Meal 2,{sample_foods[1].name},100,{sample_foods[2].name},150"""
csv_file = tmp_path / "test_meals.csv"
csv_file.write_text(csv_content)
with open(csv_file, 'rb') as f:
response = client.post("/meals/upload",
files={"file": ("test_meals.csv", f, "text/csv")})
assert response.status_code == 200
data = response.json()
assert "created" in data or "updated" in data or "errors" in data
def test_bulk_upload_meals_missing_food(self, client, tmp_path):
"""Test bulk upload with missing food"""
csv_content = """Meal Name,Food 1,Grams 1,Food 2,Grams 2
Invalid Meal,Nonexistent Food,150,Another Fake Food,200"""
csv_file = tmp_path / "invalid_meals.csv"
csv_file.write_text(csv_content)
with open(csv_file, 'rb') as f:
response = client.post("/meals/upload",
files={"file": ("invalid_meals.csv", f, "text/csv")})
assert response.status_code == 200
data = response.json()
assert "errors" in data
assert len(data["errors"]) > 0
class TestMealNutrition:
"""Test meal nutrition calculations"""
def test_meal_nutrition_calculation(self, client, sample_meal, db_session):
"""Test that meal nutrition is calculated correctly"""
from main import calculate_meal_nutrition
nutrition = calculate_meal_nutrition(sample_meal, db_session)
assert "calories" in nutrition
assert "protein" in nutrition
assert "carbs" in nutrition
assert "fat" in nutrition
assert "fiber" in nutrition
assert nutrition["calories"] > 0
def test_empty_meal_nutrition(self, client, db_session):
"""Test nutrition calculation for empty meal"""
from main import Meal, calculate_meal_nutrition
empty_meal = Meal(
name="Empty Meal",
meal_type="snack",
meal_time="Snack 1"
)
db_session.add(empty_meal)
db_session.commit()
nutrition = calculate_meal_nutrition(empty_meal, db_session)
assert nutrition["calories"] == 0
assert nutrition["protein"] == 0

184
tests/test_plans.py Normal file
View File

@@ -0,0 +1,184 @@
"""
Tests for Plans CRUD operations
"""
import pytest
from datetime import date, timedelta
class TestPlansRoutes:
"""Test plan-related routes"""
def test_get_plan_page(self, client):
"""Test GET /plan page"""
response = client.get("/plan?person=Sarah")
assert response.status_code == 200
assert b"Plan" in response.content or b"plan" in response.content
def test_get_plan_page_with_date(self, client):
"""Test GET /plan page with specific date"""
test_date = date.today().isoformat()
response = client.get(f"/plan?person=Stuart&week_start_date={test_date}")
assert response.status_code == 200
def test_add_to_plan(self, client, sample_meal):
"""Test POST /plan/add"""
test_date = date.today().isoformat()
response = client.post("/plan/add", data={
"person": "Sarah",
"plan_date": test_date,
"meal_id": str(sample_meal.id),
"meal_time": "Breakfast"
})
assert response.status_code == 200
data = response.json()
assert data["status"] == "success"
def test_add_to_plan_missing_fields(self, client):
"""Test adding to plan with missing fields"""
response = client.post("/plan/add", data={
"person": "Sarah"
# Missing plan_date, meal_id, meal_time
})
assert response.status_code == 200
data = response.json()
assert data["status"] == "error"
def test_add_to_plan_invalid_meal(self, client):
"""Test adding non-existent meal to plan"""
test_date = date.today().isoformat()
response = client.post("/plan/add", data={
"person": "Sarah",
"plan_date": test_date,
"meal_id": "99999",
"meal_time": "Breakfast"
})
assert response.status_code == 200
data = response.json()
assert data["status"] == "error"
def test_get_day_plan(self, client, sample_plan):
"""Test GET /plan/{person}/{date}"""
test_date = sample_plan.date.isoformat()
response = client.get(f"/plan/{sample_plan.person}/{test_date}")
assert response.status_code == 200
data = response.json()
assert "meals" in data
assert "day_totals" in data
assert isinstance(data["meals"], list)
def test_get_day_plan_empty(self, client):
"""Test getting plan for day with no meals"""
future_date = (date.today() + timedelta(days=365)).isoformat()
response = client.get(f"/plan/Sarah/{future_date}")
assert response.status_code == 200
data = response.json()
assert "meals" in data
assert len(data["meals"]) == 0
def test_update_day_plan(self, client, sample_meal):
"""Test POST /plan/update_day"""
test_date = date.today().isoformat()
response = client.post("/plan/update_day", data={
"person": "Stuart",
"date": test_date,
"meal_ids": f"{sample_meal.id}"
})
assert response.status_code == 200
data = response.json()
assert data["status"] == "success"
def test_update_day_plan_multiple_meals(self, client, sample_meal, sample_foods, db_session):
"""Test updating plan with multiple meals"""
from main import Meal
# Create another meal
meal2 = Meal(name="Second Meal", meal_type="lunch", meal_time="Lunch")
db_session.add(meal2)
db_session.commit()
db_session.refresh(meal2)
test_date = date.today().isoformat()
response = client.post("/plan/update_day", data={
"person": "Sarah",
"date": test_date,
"meal_ids": f"{sample_meal.id},{meal2.id}"
})
assert response.status_code == 200
data = response.json()
assert data["status"] == "success"
def test_remove_from_plan(self, client, sample_plan):
"""Test DELETE /plan/{plan_id}"""
response = client.delete(f"/plan/{sample_plan.id}")
assert response.status_code == 200
data = response.json()
assert data["status"] == "success"
def test_remove_nonexistent_plan(self, client):
"""Test removing non-existent plan"""
response = client.delete("/plan/99999")
assert response.status_code == 200
data = response.json()
assert data["status"] == "error"
class TestPlanNavigation:
"""Test plan navigation functionality"""
def test_plan_week_navigation(self, client, sample_meal):
"""Test navigating between weeks"""
# Get current week
response = client.get("/plan?person=Sarah")
assert response.status_code == 200
# Add meal to today
test_date = date.today().isoformat()
client.post("/plan/add", data={
"person": "Sarah",
"plan_date": test_date,
"meal_id": str(sample_meal.id),
"meal_time": "Breakfast"
})
# Get next week
next_week = (date.today() + timedelta(days=7)).isoformat()
response = client.get(f"/plan?person=Sarah&week_start_date={next_week}")
assert response.status_code == 200
# Get previous week
prev_week = (date.today() - timedelta(days=7)).isoformat()
response = client.get(f"/plan?person=Sarah&week_start_date={prev_week}")
assert response.status_code == 200
class TestDayNutrition:
"""Test day nutrition calculations"""
def test_calculate_day_nutrition(self, client, sample_plan, db_session):
"""Test day nutrition calculation"""
from main import calculate_day_nutrition, Plan
plans = db_session.query(Plan).filter(
Plan.person == sample_plan.person,
Plan.date == sample_plan.date
).all()
nutrition = calculate_day_nutrition(plans, db_session)
assert "calories" in nutrition
assert "protein" in nutrition
assert "carbs" in nutrition
assert "fat" in nutrition
assert "protein_pct" in nutrition
assert "carbs_pct" in nutrition
assert "fat_pct" in nutrition
def test_empty_day_nutrition(self, db_session):
"""Test nutrition calculation for day with no meals"""
from main import calculate_day_nutrition
nutrition = calculate_day_nutrition([], db_session)
assert nutrition["calories"] == 0
assert nutrition["protein"] == 0
assert nutrition["protein_pct"] == 0

157
tests/test_templates.py Normal file
View File

@@ -0,0 +1,157 @@
import sys
import os
print(f"DEBUG: Running with Python executable: {sys.executable}")
print(f"DEBUG: Python version: {sys.version}")
import pytest
from fastapi.testclient import TestClient
from main import app, get_db, SessionLocal, engine, Base, Template, TemplateMeal, Meal, MealFood, Food
from sqlalchemy.orm import sessionmaker
from sqlalchemy import create_engine
# Setup test database
SQLALCHEMY_DATABASE_URL = "sqlite:///./test_meal_planner.db"
test_engine = create_engine(SQLALCHEMY_DATABASE_URL, connect_args={"check_same_thread": False})
TestingSessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=test_engine)
@pytest.fixture(name="session")
def session_fixture():
Base.metadata.create_all(bind=test_engine)
db = TestingSessionLocal()
try:
yield db
finally:
db.close()
Base.metadata.drop_all(bind=test_engine)
@pytest.fixture(name="client")
def client_fixture(session):
def override_get_db():
yield session
app.dependency_overrides[get_db] = override_get_db
with TestClient(app) as client:
yield client
app.dependency_overrides.clear()
def test_templates_page(client, session):
response = client.get("/templates")
assert response.status_code == 200
assert "Meal Templates" in response.text
def test_create_template(client, session):
# Create a food and a meal first
food1 = Food(name="Apple", serving_size="1", serving_unit="medium", calories=95, protein=0.5, carbs=25, fat=0.3)
session.add(food1)
session.commit()
session.refresh(food1)
meal1 = Meal(name="Fruit Salad", meal_type="breakfast", meal_time="Breakfast")
session.add(meal1)
session.commit()
session.refresh(meal1)
meal_food1 = MealFood(meal_id=meal1.id, food_id=food1.id, quantity=1.0)
session.add(meal_food1)
session.commit()
response = client.post(
"/templates/create",
data={"name": "Test Template", "meal_assignments": f"Breakfast:{meal1.id},Lunch:"}
)
assert response.status_code == 200
assert response.json() == {"status": "success", "message": "Template created successfully"}
template = session.query(Template).filter(Template.name == "Test Template").first()
assert template is not None
assert len(template.template_meals) == 1
assert template.template_meals[0].meal_time == "Breakfast"
assert template.template_meals[0].meal_id == meal1.id
def test_create_template_duplicate_name(client, session):
template = Template(name="Existing Template")
session.add(template)
session.commit()
response = client.post("/templates/create", data={"name": "Existing Template"})
assert response.status_code == 200
assert response.json() == {"status": "error", "message": "Template with name 'Existing Template' already exists"}
def test_get_template_details(client, session):
template = Template(name="Detail Template")
session.add(template)
session.commit()
session.refresh(template)
response = client.get(f"/templates/{template.id}")
assert response.status_code == 200
assert response.json()["status"] == "success"
assert response.json()["template"]["name"] == "Detail Template"
def test_update_template(client, session):
food1 = Food(name="Orange", serving_size="1", serving_unit="medium", calories=62, protein=1.2, carbs=15.4, fat=0.2)
session.add(food1)
session.commit()
session.refresh(food1)
meal1 = Meal(name="Orange Juice", meal_type="breakfast", meal_time="Breakfast")
session.add(meal1)
session.commit()
session.refresh(meal1)
template = Template(name="Update Template")
session.add(template)
session.commit()
session.refresh(template)
response = client.put(
f"/templates/{template.id}",
data={"name": "Updated Template Name", "meal_assignments": f"Breakfast:{meal1.id}"}
)
assert response.status_code == 200
assert response.json() == {"status": "success", "message": "Template updated successfully"}
updated_template = session.query(Template).filter(Template.id == template.id).first()
assert updated_template.name == "Updated Template Name"
assert len(updated_template.template_meals) == 1
assert updated_template.template_meals[0].meal_time == "Breakfast"
assert updated_template.template_meals[0].meal_id == meal1.id
def test_delete_template(client, session):
template = Template(name="Delete Template")
session.add(template)
session.commit()
session.refresh(template)
response = client.delete(f"/templates/{template.id}")
assert response.status_code == 200
assert response.json() == {"status": "success"}
deleted_template = session.query(Template).filter(Template.id == template.id).first()
assert deleted_template is None
def test_use_template(client, session):
food1 = Food(name="Banana", serving_size="1", serving_unit="medium", calories=105, protein=1.3, carbs=27, fat=0.4)
session.add(food1)
session.commit()
session.refresh(food1)
meal1 = Meal(name="Banana Smoothie", meal_type="breakfast", meal_time="Breakfast")
session.add(meal1)
session.commit()
session.refresh(meal1)
template = Template(name="Use Template")
session.add(template)
session.commit()
session.refresh(template)
template_meal = TemplateMeal(template_id=template.id, meal_id=meal1.id, meal_time="Breakfast")
session.add(template_meal)
session.commit()
response = client.post(
f"/templates/{template.id}/use",
data={"person": "Sarah", "start_date": "2025-01-01"}
)
assert response.status_code == 200
assert response.json() == {"status": "success", "message": "Template applied successfully"}

215
tests/test_tracker.py Normal file
View File

@@ -0,0 +1,215 @@
"""
Tests for Tracker CRUD operations
"""
import pytest
from datetime import date, timedelta
class TestTrackerRoutes:
"""Test tracker-related routes"""
def test_get_tracker_page(self, client):
"""Test GET /tracker page"""
response = client.get("/tracker?person=Sarah")
assert response.status_code == 200
assert b"Tracker" in response.content or b"tracker" in response.content
def test_get_tracker_page_with_date(self, client):
"""Test GET /tracker page with specific date"""
test_date = date.today().isoformat()
response = client.get(f"/tracker?person=Stuart&date={test_date}")
assert response.status_code == 200
def test_tracker_add_meal(self, client, sample_meal):
"""Test POST /tracker/add_meal"""
test_date = date.today().isoformat()
response = client.post("/tracker/add_meal", data={
"person": "Sarah",
"date": test_date,
"meal_id": str(sample_meal.id),
"meal_time": "Breakfast",
"quantity": "1.5"
})
assert response.status_code == 200
data = response.json()
assert data["status"] == "success"
def test_tracker_add_meal_default_quantity(self, client, sample_meal):
"""Test adding meal with default quantity"""
test_date = date.today().isoformat()
response = client.post("/tracker/add_meal", data={
"person": "Stuart",
"date": test_date,
"meal_id": str(sample_meal.id),
"meal_time": "Lunch"
})
assert response.status_code == 200
data = response.json()
assert data["status"] == "success"
def test_tracker_remove_meal(self, client, sample_tracked_day, db_session):
"""Test DELETE /tracker/remove_meal/{tracked_meal_id}"""
from main import TrackedMeal
tracked_meal = db_session.query(TrackedMeal).filter(
TrackedMeal.tracked_day_id == sample_tracked_day.id
).first()
if tracked_meal:
response = client.delete(f"/tracker/remove_meal/{tracked_meal.id}")
assert response.status_code == 200
data = response.json()
assert data["status"] == "success"
def test_tracker_remove_nonexistent_meal(self, client):
"""Test removing non-existent tracked meal"""
response = client.delete("/tracker/remove_meal/99999")
assert response.status_code == 200
data = response.json()
assert data["status"] == "error"
class TestTrackerTemplates:
"""Test tracker template functionality"""
def test_tracker_save_template(self, client, sample_tracked_day):
"""Test POST /tracker/save_template"""
test_date = sample_tracked_day.date.isoformat()
response = client.post("/tracker/save_template", data={
"person": sample_tracked_day.person,
"date": test_date,
"template_name": "New Saved Template"
})
assert response.status_code == 200
data = response.json()
assert data["status"] == "success"
def test_tracker_save_template_no_meals(self, client):
"""Test saving template from day with no meals"""
future_date = (date.today() + timedelta(days=365)).isoformat()
response = client.post("/tracker/save_template", data={
"person": "Sarah",
"date": future_date,
"template_name": "Empty Template"
})
assert response.status_code == 200
data = response.json()
assert data["status"] == "error"
def test_tracker_apply_template(self, client, sample_template):
"""Test POST /tracker/apply_template"""
test_date = date.today().isoformat()
response = client.post("/tracker/apply_template", data={
"person": "Sarah",
"date": test_date,
"template_id": str(sample_template.id)
})
assert response.status_code == 200
data = response.json()
assert data["status"] == "success"
def test_tracker_apply_nonexistent_template(self, client):
"""Test applying non-existent template"""
test_date = date.today().isoformat()
response = client.post("/tracker/apply_template", data={
"person": "Sarah",
"date": test_date,
"template_id": "99999"
})
assert response.status_code == 200
data = response.json()
assert data["status"] == "error"
def test_tracker_apply_empty_template(self, client, db_session):
"""Test applying template with no meals"""
from main import Template
empty_template = Template(name="Empty Tracker Template")
db_session.add(empty_template)
db_session.commit()
db_session.refresh(empty_template)
test_date = date.today().isoformat()
response = client.post("/tracker/apply_template", data={
"person": "Sarah",
"date": test_date,
"template_id": str(empty_template.id)
})
assert response.status_code == 200
data = response.json()
assert data["status"] == "error"
class TestTrackerReset:
"""Test tracker reset functionality"""
def test_tracker_reset_to_plan(self, client, sample_tracked_day):
"""Test POST /tracker/reset_to_plan"""
test_date = sample_tracked_day.date.isoformat()
response = client.post("/tracker/reset_to_plan", data={
"person": sample_tracked_day.person,
"date": test_date
})
assert response.status_code == 200
data = response.json()
assert data["status"] == "success"
def test_tracker_reset_nonexistent_day(self, client):
"""Test resetting non-existent tracked day"""
future_date = (date.today() + timedelta(days=365)).isoformat()
response = client.post("/tracker/reset_to_plan", data={
"person": "Sarah",
"date": future_date
})
assert response.status_code == 200
data = response.json()
assert data["status"] == "error"
class TestTrackerNutrition:
"""Test tracker nutrition calculations"""
def test_calculate_tracked_day_nutrition(self, client, sample_tracked_day, db_session):
"""Test tracked day nutrition calculation"""
from main import calculate_day_nutrition_tracked, TrackedMeal
tracked_meals = db_session.query(TrackedMeal).filter(
TrackedMeal.tracked_day_id == sample_tracked_day.id
).all()
nutrition = calculate_day_nutrition_tracked(tracked_meals, db_session)
assert "calories" in nutrition
assert "protein" in nutrition
assert "carbs" in nutrition
assert "fat" in nutrition
assert nutrition["calories"] >= 0
def test_tracked_day_with_quantity_multiplier(self, client, sample_meal, db_session):
"""Test nutrition calculation with quantity multiplier"""
from main import TrackedDay, TrackedMeal, calculate_day_nutrition_tracked
# Create tracked day with meal at 2x quantity
tracked_day = TrackedDay(
person="Sarah",
date=date.today(),
is_modified=True
)
db_session.add(tracked_day)
db_session.commit()
db_session.refresh(tracked_day)
tracked_meal = TrackedMeal(
tracked_day_id=tracked_day.id,
meal_id=sample_meal.id,
meal_time="Breakfast",
quantity=2.0
)
db_session.add(tracked_meal)
db_session.commit()
tracked_meals = [tracked_meal]
nutrition = calculate_day_nutrition_tracked(tracked_meals, db_session)
# Should be double the base meal nutrition
assert nutrition["calories"] > 0

62
tests/test_weekly_menu.py Normal file
View File

@@ -0,0 +1,62 @@
"""
Tests for Weekly Menu operations
"""
import pytest
class TestWeeklyMenuRoutes:
"""Test weekly menu-related routes"""
def test_get_weekly_menu_page(self, client):
"""Test GET /weeklymenu page"""
response = client.get("/weeklymenu")
assert response.status_code == 200
assert b"Weekly" in response.content or b"weekly" in response.content or b"Menu" in response.content
class TestWeeklyMenuCRUD:
"""Test weekly menu CRUD operations"""
def test_create_weekly_menu(self, client, db_session, sample_template):
"""Test creating a weekly menu"""
from main import WeeklyMenu, WeeklyMenuDay
weekly_menu = WeeklyMenu(name="Test Weekly Menu")
db_session.add(weekly_menu)
db_session.commit()
db_session.refresh(weekly_menu)
# Add days to weekly menu
for day in range(7):
menu_day = WeeklyMenuDay(
weekly_menu_id=weekly_menu.id,
day_of_week=day,
template_id=sample_template.id
)
db_session.add(menu_day)
db_session.commit()
assert weekly_menu.id is not None
assert len(weekly_menu.weekly_menu_days) == 7
def test_weekly_menu_relationships(self, client, db_session, sample_template):
"""Test weekly menu relationships"""
from main import WeeklyMenu, WeeklyMenuDay
weekly_menu = WeeklyMenu(name="Relationship Test Menu")
db_session.add(weekly_menu)
db_session.commit()
db_session.refresh(weekly_menu)
menu_day = WeeklyMenuDay(
weekly_menu_id=weekly_menu.id,
day_of_week=0, # Monday
template_id=sample_template.id
)
db_session.add(menu_day)
db_session.commit()
# Verify relationships
assert menu_day.weekly_menu.id == weekly_menu.id
assert menu_day.template.id == sample_template.id