Files
foodplanner/tests/test_tracker.py

463 lines
17 KiB
Python

"""
Tests for Tracker CRUD operations
"""
import pytest
from datetime import date, timedelta
from app.database import (
TrackedDay, TrackedMeal, TrackedMealFood, Meal, MealFood, Food,
Template, calculate_day_nutrition_tracked
)
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"
})
assert response.status_code == 200
data = response.json()
assert data["status"] == "success"
# This test is no longer relevant as quantity is always 1.0
# 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}"""
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"""
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"""
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"""
# Create tracked day with meal
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"
)
db_session.add(tracked_meal)
db_session.commit()
tracked_meals = [tracked_meal]
nutrition = calculate_day_nutrition_tracked(tracked_meals, db_session)
# Should be the base meal nutrition
assert nutrition["calories"] > 0
class TestTrackerView:
"""Test tracker view rendering"""
def test_tracker_page_shows_food_breakdown(self, client, sample_meal, sample_food, db_session):
"""Test that tracker page shows food breakdown for tracked meals"""
# Create sample tracked day and meal
tracked_day = TrackedDay(person="Sarah", date=date.today(), is_modified=True)
db_session.add(tracked_day)
db_session.commit()
db_session.refresh(tracked_day)
# Add the meal to tracker (assuming meal has the food)
tracked_meal = TrackedMeal(
tracked_day_id=tracked_day.id,
meal_id=sample_meal.id,
meal_time="Breakfast"
)
db_session.add(tracked_meal)
db_session.commit()
# Get tracker page
response = client.get(f"/tracker?person=Sarah")
assert response.status_code == 200
# Check if food name appears in the response (breakdown should show it)
assert sample_food.name.encode() in response.content
class TestTrackerEdit:
"""Test editing tracked meals"""
def test_update_tracked_food_quantity(self, client, sample_meal, sample_food, db_session):
"""Test updating quantity of a custom food in a tracked meal"""
# Create sample tracked day and meal
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"
)
db_session.add(tracked_meal)
db_session.commit()
db_session.refresh(tracked_meal)
# Add a custom tracked food
tracked_food = TrackedMealFood(
tracked_meal_id=tracked_meal.id,
food_id=sample_food.id,
quantity=2.0,
is_override=True
)
db_session.add(tracked_food)
db_session.commit()
db_session.refresh(tracked_food)
original_quantity = tracked_food.quantity
# Update the food quantity via API
response = client.post("/tracker/update_tracked_food", json={
"tracked_food_id": tracked_food.id,
"quantity": 3.0,
"is_custom": True
})
assert response.status_code == 200
data = response.json()
assert data["status"] == "success"
# Verify the update
db_session.commit()
updated_food = db_session.query(TrackedMealFood).get(tracked_food.id)
assert updated_food.quantity == 3.0
assert updated_food.quantity != original_quantity
class TestTrackerSaveAsNewMeal:
"""Test saving an edited tracked meal as a new meal"""
def test_save_as_new_meal(self, client, sample_meal, sample_food, db_session):
"""Test POST /tracker/save_as_new_meal"""
# Create a tracked day and meal with custom foods
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"
)
db_session.add(tracked_meal)
db_session.commit()
db_session.refresh(tracked_meal)
# Add a custom food to the tracked meal
tracked_food = TrackedMealFood(
tracked_meal_id=tracked_meal.id,
food_id=sample_food.id,
quantity=2.5,
is_override=False # This is an addition, not an override for this test
)
db_session.add(tracked_food)
db_session.commit()
db_session.refresh(tracked_food)
new_meal_name = "My Custom Breakfast"
response = client.post("/tracker/save_as_new_meal", json={
"tracked_meal_id": tracked_meal.id,
"new_meal_name": new_meal_name,
"foods": [
{"food_id": sample_food.id, "quantity": 3.0}
]
})
assert response.status_code == 200
data = response.json()
assert data["status"] == "success"
assert "new_meal_id" in data
# Verify a new meal was created
new_meal = db_session.query(Meal).filter(Meal.name == new_meal_name).first()
assert new_meal is not None
assert len(new_meal.meal_foods) == 1 # Only the custom food should be here
# Verify the original tracked meal now points to the new meal
db_session.commit()
updated_tracked_meal = db_session.query(TrackedMeal).get(tracked_meal.id)
assert updated_tracked_meal.meal_id == new_meal.id
assert len(updated_tracked_meal.tracked_foods) == 0 # Custom foods should be moved to the new meal
class TestTrackerAddFood:
"""Test adding a single food directly to the tracker"""
def test_add_food_to_tracker(self, client, sample_food, db_session):
"""Test POST /tracker/add_food"""
# Create a tracked day
tracked_day = TrackedDay(person="Sarah", date=date.today(), is_modified=False)
db_session.add(tracked_day)
db_session.commit()
db_session.refresh(tracked_day)
# Add food directly to tracker
response = client.post("/tracker/add_food", json={
"person": "Sarah",
"date": date.today().isoformat(),
"food_id": sample_food.id,
"quantity": 100.0,
"meal_time": "Snack 1"
})
assert response.status_code == 200
data = response.json()
assert data["status"] == "success"
# Verify that a new tracked meal was created with the food
tracked_meals = db_session.query(TrackedMeal).filter(
TrackedMeal.tracked_day_id == tracked_day.id,
TrackedMeal.meal_time == "Snack 1"
).all()
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
# 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
def test_add_food_to_tracker_with_meal_time(self, client, sample_food, db_session):
"""Test POST /tracker/add_food with a specific meal time"""
tracked_day = TrackedDay(person="Sarah", date=date.today(), is_modified=False)
db_session.add(tracked_day)
db_session.commit()
db_session.refresh(tracked_day)
response = client.post("/tracker/add_food", json={
"person": "Sarah",
"date": date.today().isoformat(),
"food_id": sample_food.id,
"quantity": 150.0,
"meal_time": "Dinner"
})
assert response.status_code == 200
data = response.json()
assert data["status"] == "success"
tracked_meals = db_session.query(TrackedMeal).filter(
TrackedMeal.tracked_day_id == tracked_day.id,
TrackedMeal.meal_time == "Dinner"
).all()
assert len(tracked_meals) == 1
tracked_meal = tracked_meals[0]
assert tracked_meal.meal.name == sample_food.name
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
class TestTrackedMealQuantity:
"""Test the quantity of a tracked meal"""
def test_add_meal_quantity_is_one(self, client, sample_meal, db_session):
"""Test that when a meal is added to the tracker, its quantity is 1.0"""
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"
})
assert response.status_code == 200
data = response.json()
assert data["status"] == "success"
tracked_meal = db_session.query(TrackedMeal).order_by(TrackedMeal.id.desc()).first()
assert tracked_meal is not None
def test_add_food_quantity_is_one(self, client, sample_food, db_session):
"""Test that when a single food is added to the tracker, the underlying meal quantity is 1.0"""
test_date = date.today().isoformat()
response = client.post("/tracker/add_food", json={
"person": "Sarah",
"date": test_date,
"food_id": sample_food.id,
"quantity": 100.0,
"meal_time": "Snack 1"
})
assert response.status_code == 200
data = response.json()
assert data["status"] == "success"
tracked_meal = db_session.query(TrackedMeal).order_by(TrackedMeal.id.desc()).first()
assert tracked_meal is not None