diff --git a/app/api/routes/tracker.py b/app/api/routes/tracker.py index 7dbba84..f444ada 100644 --- a/app/api/routes/tracker.py +++ b/app/api/routes/tracker.py @@ -746,23 +746,25 @@ async def tracker_add_food(data: dict = Body(...), db: Session = Depends(get_db) # Store grams directly quantity = grams - # Create a new Meal for this single food entry - # This allows it to be treated like any other meal in the tracker view - new_meal = Meal(name=food_item.name, meal_type="single_food", meal_time=meal_time) - db.add(new_meal) - db.flush() # Flush to get the new meal ID - - # Link the food to the new meal - meal_food = MealFood(meal_id=new_meal.id, food_id=food_id, quantity=grams) - db.add(meal_food) - - # Create tracked meal entry + # Create tracked meal entry without a parent Meal template tracked_meal = TrackedMeal( tracked_day_id=tracked_day.id, - meal_id=new_meal.id, - meal_time=meal_time + meal_id=None, + meal_time=meal_time, + name=food_item.name ) db.add(tracked_meal) + db.flush() # Flush to get the tracked_meal ID + + # Link the food directly to the tracked meal via TrackedMealFood + new_entry = TrackedMealFood( + tracked_meal_id=tracked_meal.id, + food_id=food_id, + quantity=grams, + is_override=False, + is_deleted=False + ) + db.add(new_entry) # Mark day as modified tracked_day.is_modified = True diff --git a/app/database.py b/app/database.py index abd838e..dcb4291 100644 --- a/app/database.py +++ b/app/database.py @@ -428,9 +428,11 @@ def calculate_tracked_meal_nutrition(tracked_meal, db: Session): 'fiber': 0, 'sugar': 0, 'sodium': 0, 'calcium': 0 } - # 1. Get base foods from the meal + # 1. Get base foods from the meal (if it exists) # access via relationship, assume eager loading or lazy loading - base_foods = {mf.food_id: mf for mf in tracked_meal.meal.meal_foods} + base_foods = {} + if tracked_meal.meal: + base_foods = {mf.food_id: mf for mf in tracked_meal.meal.meal_foods} # 2. Get tracked foods (overrides, deletions, additions) tracked_foods = tracked_meal.tracked_foods diff --git a/tests/test_add_food_bug.py b/tests/test_add_food_bug.py index b57b4bb..5490b20 100644 --- a/tests/test_add_food_bug.py +++ b/tests/test_add_food_bug.py @@ -98,20 +98,9 @@ def test_add_food_quantity_saved_correctly(test_client: TestClient, test_engine) query_session = sessionmaker(autocommit=False, autoflush=False, bind=test_engine)() try: - # Find the created Meal - created_meal = query_session.query(Meal).order_by(Meal.id.desc()).first() - assert created_meal is not None - assert created_meal.name == "Test Food" - assert created_meal.meal_type == "single_food" - - # Find the MealFood - meal_food = query_session.query(MealFood).filter(MealFood.meal_id == created_meal.id).first() - assert meal_food is not None - assert meal_food.food_id == food.id - - # This assertion fails because the backend used data.get("grams", 1.0), so quantity=1.0 instead of 50.0 - # After the fix changing to data.get("quantity", 1.0), it will pass - assert meal_food.quantity == 50.0, f"Expected quantity 50.0, but got {meal_food.quantity}" + # Verify NO new Meal was created + meals = query_session.query(Meal).all() + assert len(meals) == 0 # Also verify TrackedDay and TrackedMeal were created tracked_day = query_session.query(TrackedDay).filter( @@ -123,8 +112,16 @@ def test_add_food_quantity_saved_correctly(test_client: TestClient, test_engine) tracked_meal = query_session.query(TrackedMeal).filter(TrackedMeal.tracked_day_id == tracked_day.id).first() assert tracked_meal is not None - assert tracked_meal.meal_id == created_meal.id + assert tracked_meal.meal_id is None + assert tracked_meal.name == "Test Food" assert tracked_meal.meal_time == "Snack 1" + + # Find the TrackedMealFood + from app.database import TrackedMealFood + tmf = query_session.query(TrackedMealFood).filter(TrackedMealFood.tracked_meal_id == tracked_meal.id).first() + assert tmf is not None + assert tmf.food_id == food.id + assert tmf.quantity == 50.0 finally: query_session.close() diff --git a/tests/test_food_weight_consistency.py b/tests/test_food_weight_consistency.py index 4e592a7..8e20dff 100644 --- a/tests/test_food_weight_consistency.py +++ b/tests/test_food_weight_consistency.py @@ -140,10 +140,13 @@ def test_tracker_add_food_grams_input(client, session, sample_food_100g): assert response.json()["status"] == "success" # Verify the tracked meal food quantity - tracked_meal = session.query(Meal).filter(Meal.name == sample_food_100g.name).first() + tracked_meal = session.query(TrackedMeal).filter(TrackedMeal.name == sample_food_100g.name).first() assert tracked_meal is not None - meal_food = session.query(MealFood).filter(MealFood.meal_id == tracked_meal.id).first() - assert meal_food.quantity == grams + assert tracked_meal.meal_id is None + + tmf = session.query(TrackedMealFood).filter(TrackedMealFood.tracked_meal_id == tracked_meal.id).first() + assert tmf is not None + assert tmf.quantity == grams def test_update_tracked_meal_foods_grams_input(client, session, sample_food_100g, sample_food_50g): """Test updating tracked meal foods with grams input""" diff --git a/tests/test_tracked_meal_refactor.py b/tests/test_tracked_meal_refactor.py new file mode 100644 index 0000000..480a617 --- /dev/null +++ b/tests/test_tracked_meal_refactor.py @@ -0,0 +1,113 @@ +import pytest +from app.database import Food, TrackedMeal, TrackedMealFood, calculate_tracked_meal_nutrition +from sqlalchemy.orm import Session + +def test_calculate_tracked_meal_nutrition_no_meal_template(db_session: Session): + """Test nutrition calculation for a tracked meal with no parent meal template (meal_id=None)""" + # Create a food + food = Food( + name="Test Food", + serving_size=100.0, + serving_unit="g", + calories=100.0, + protein=10.0, + carbs=20.0, + fat=5.0, + fiber=5.0, + sugar=10.0, + sodium=100.0, + calcium=50.0 + ) + db_session.add(food) + db_session.commit() + db_session.refresh(food) + + # Create a tracked meal without a template + tracked_meal = TrackedMeal( + meal_id=None, + meal_time="Snack", + name="Single Food Log" + ) + db_session.add(tracked_meal) + db_session.commit() + db_session.refresh(tracked_meal) + + # Add a tracked food entry to it + tracked_food = TrackedMealFood( + tracked_meal_id=tracked_meal.id, + food_id=food.id, + quantity=200.0, # 2 servings + is_override=False, + is_deleted=False + ) + db_session.add(tracked_food) + db_session.commit() + db_session.refresh(tracked_food) + + # Calculate nutrition + nutrition = calculate_tracked_meal_nutrition(tracked_meal, db_session) + + # Assertions + assert nutrition['calories'] == 200.0 + assert nutrition['protein'] == 20.0 + assert nutrition['carbs'] == 40.0 + assert nutrition['fat'] == 10.0 + assert nutrition['fiber'] == 10.0 + assert nutrition['sugar'] == 20.0 + assert nutrition['sodium'] == 200.0 + assert nutrition['calcium'] == 100.0 + assert nutrition['net_carbs'] == 30.0 + assert nutrition['protein_pct'] == 40.0 # (20 * 4) / 200 = 80 / 200 = 40% + assert nutrition['carbs_pct'] == 80.0 # (40 * 4) / 200 = 160 / 200 = 80% + assert nutrition['fat_pct'] == 45.0 # (10 * 9) / 200 = 90 / 200 = 45% + +def test_tracker_add_food_api_no_new_meal(client, db_session: Session): + """Test /tracker/add_food endpoint to ensure it doesn't create redundant Meal templates""" + # Create a food + food = Food( + name="API Test Food", + serving_size=100.0, + serving_unit="g", + calories=100.0, + protein=10.0, + carbs=20.0, + fat=5.0 + ) + db_session.add(food) + db_session.commit() + db_session.refresh(food) + + from app.database import Meal + initial_meal_count = db_session.query(Meal).count() + + # Call the API + response = client.post("/tracker/add_food", json={ + "person": "Sarah", + "date": "2025-02-24", + "food_id": food.id, + "quantity": 150.0, + "meal_time": "Snack" + }) + + assert response.status_code == 200 + assert response.json()["status"] == "success" + + # Verify NO new Meal was created + assert db_session.query(Meal).count() == initial_meal_count + + # Verify TrackedMeal exists with meal_id=None and correct name + from app.database import TrackedMeal, TrackedDay + tracked_day = db_session.query(TrackedDay).filter(TrackedDay.date == "2025-02-24").first() + assert tracked_day is not None + + tracked_meal = db_session.query(TrackedMeal).filter(TrackedMeal.tracked_day_id == tracked_day.id).first() + assert tracked_meal is not None + assert tracked_meal.meal_id is None + assert tracked_meal.name == "API Test Food" + + # Verify TrackedMealFood exists + from app.database import TrackedMealFood + tmf = db_session.query(TrackedMealFood).filter(TrackedMealFood.tracked_meal_id == tracked_meal.id).first() + assert tmf is not None + assert tmf.food_id == food.id + assert tmf.quantity == 150.0 diff --git a/tests/test_tracker.py b/tests/test_tracker.py index b02f76c..cfd5359 100644 --- a/tests/test_tracker.py +++ b/tests/test_tracker.py @@ -384,12 +384,13 @@ class TestTrackerAddFood: assert len(tracked_meals) == 1 tracked_meal = tracked_meals[0] - assert tracked_meal.meal.name == sample_food.name # The meal name should be the food name + assert tracked_meal.name == sample_food.name # The meal name should be the food name + assert tracked_meal.meal_id is None # Verify the food is in the tracked meal's foods - assert len(tracked_meal.meal.meal_foods) == 1 - assert tracked_meal.meal.meal_foods[0].food_id == sample_food.id - assert tracked_meal.meal.meal_foods[0].quantity == 100.0 + assert len(tracked_meal.tracked_foods) == 1 + assert tracked_meal.tracked_foods[0].food_id == sample_food.id + assert tracked_meal.tracked_foods[0].quantity == 100.0 def test_add_food_to_tracker_with_meal_time(self, client, sample_food, db_session): @@ -418,11 +419,12 @@ class TestTrackerAddFood: assert len(tracked_meals) == 1 tracked_meal = tracked_meals[0] - assert tracked_meal.meal.name == sample_food.name + assert tracked_meal.name == sample_food.name + assert tracked_meal.meal_id is None - assert len(tracked_meal.meal.meal_foods) == 1 - assert tracked_meal.meal.meal_foods[0].food_id == sample_food.id - assert tracked_meal.meal.meal_foods[0].quantity == 150.0 + assert len(tracked_meal.tracked_foods) == 1 + assert tracked_meal.tracked_foods[0].food_id == sample_food.id + assert tracked_meal.tracked_foods[0].quantity == 150.0 def test_add_food_quantity_is_correctly_converted_to_servings(self, client, db_session): """ @@ -464,12 +466,13 @@ class TestTrackerAddFood: assert len(tracked_meals) == 1 tracked_meal = tracked_meals[0] - assert tracked_meal.meal.name == food.name + assert tracked_meal.name == food.name + assert tracked_meal.meal_id is None - # Verify the food is in the tracked meal's foods and quantity is in servings - assert len(tracked_meal.meal.meal_foods) == 1 - assert tracked_meal.meal.meal_foods[0].food_id == food.id - assert tracked_meal.meal.meal_foods[0].quantity == grams_to_add + # Verify the food is in the tracked meal's foods + assert len(tracked_meal.tracked_foods) == 1 + assert tracked_meal.tracked_foods[0].food_id == food.id + assert tracked_meal.tracked_foods[0].quantity == grams_to_add # Verify nutrition calculation day_nutrition = calculate_day_nutrition_tracked([tracked_meal], db_session)