mirror of
https://github.com/sstent/foodplanner.git
synced 2026-01-25 03:01:35 +00:00
fixed quantitoes
This commit is contained in:
@@ -6,7 +6,7 @@ import logging
|
||||
from typing import List, Optional
|
||||
|
||||
# Import from the database module
|
||||
from app.database import get_db, Food, Meal, MealFood
|
||||
from app.database import get_db, Food, Meal, MealFood, convert_grams_to_quantity
|
||||
from main import templates
|
||||
|
||||
router = APIRouter()
|
||||
@@ -46,7 +46,8 @@ async def bulk_upload_meals(file: UploadFile = File(...), db: Session = Depends(
|
||||
continue
|
||||
|
||||
food_name = row[i].strip()
|
||||
quantity = round(float(row[i+1].strip()) / 100, 3) # Convert grams to 100g units and round to 3 decimal places
|
||||
grams = float(row[i+1].strip())
|
||||
quantity = convert_grams_to_quantity(food.id, grams, db)
|
||||
|
||||
# Try multiple matching strategies for food names
|
||||
food = None
|
||||
@@ -178,14 +179,18 @@ async def get_meal_foods(meal_id: int, db: Session = Depends(get_db)):
|
||||
return {"status": "error", "message": str(e)}
|
||||
|
||||
@router.post("/meals/{meal_id}/add_food")
|
||||
async def add_food_to_meal(meal_id: int, food_id: int = Form(...),
|
||||
quantity: float = Form(...), db: Session = Depends(get_db)):
|
||||
async def add_food_to_meal(meal_id: int, food_id: int = Form(...),
|
||||
grams: float = Form(..., alias="quantity"), db: Session = Depends(get_db)):
|
||||
|
||||
try:
|
||||
quantity = convert_grams_to_quantity(food_id, grams, db)
|
||||
meal_food = MealFood(meal_id=meal_id, food_id=food_id, quantity=quantity)
|
||||
db.add(meal_food)
|
||||
db.commit()
|
||||
return {"status": "success"}
|
||||
except ValueError as ve:
|
||||
db.rollback()
|
||||
return {"status": "error", "message": str(ve)}
|
||||
except Exception as e:
|
||||
db.rollback()
|
||||
return {"status": "error", "message": str(e)}
|
||||
@@ -206,16 +211,20 @@ async def remove_food_from_meal(meal_food_id: int, db: Session = Depends(get_db)
|
||||
return {"status": "error", "message": str(e)}
|
||||
|
||||
@router.post("/meals/update_food_quantity")
|
||||
async def update_meal_food_quantity(meal_food_id: int = Form(...), quantity: float = Form(...), db: Session = Depends(get_db)):
|
||||
async def update_meal_food_quantity(meal_food_id: int = Form(...), grams: float = Form(..., alias="quantity"), db: Session = Depends(get_db)):
|
||||
"""Update the quantity of a food in a meal"""
|
||||
try:
|
||||
meal_food = db.query(MealFood).filter(MealFood.id == meal_food_id).first()
|
||||
if not meal_food:
|
||||
return {"status": "error", "message": "Meal food not found"}
|
||||
|
||||
quantity = convert_grams_to_quantity(meal_food.food_id, grams, db)
|
||||
meal_food.quantity = quantity
|
||||
db.commit()
|
||||
return {"status": "success"}
|
||||
except ValueError as ve:
|
||||
db.rollback()
|
||||
return {"status": "error", "message": str(ve)}
|
||||
except Exception as e:
|
||||
db.rollback()
|
||||
return {"status": "error", "message": str(e)}
|
||||
|
||||
@@ -6,7 +6,7 @@ import logging
|
||||
from typing import List, Optional
|
||||
|
||||
# Import from the database module
|
||||
from app.database import get_db, Meal, Template, TemplateMeal, TrackedDay, TrackedMeal, calculate_meal_nutrition, MealFood, TrackedMealFood, Food, calculate_day_nutrition_tracked
|
||||
from app.database import get_db, Meal, Template, TemplateMeal, TrackedDay, TrackedMeal, calculate_meal_nutrition, MealFood, TrackedMealFood, Food, calculate_day_nutrition_tracked, convert_grams_to_quantity
|
||||
from main import templates
|
||||
|
||||
router = APIRouter()
|
||||
@@ -448,6 +448,93 @@ async def add_food_to_tracked_meal(data: dict = Body(...), db: Session = Depends
|
||||
logging.error(f"DEBUG: Error adding food to tracked meal: {e}")
|
||||
return {"status": "error", "message": str(e)}
|
||||
|
||||
@router.post("/tracker/update_tracked_meal_foods")
|
||||
async def update_tracked_meal_foods(data: dict = Body(...), db: Session = Depends(get_db)):
|
||||
"""Update quantities of multiple foods in a tracked meal"""
|
||||
try:
|
||||
tracked_meal_id = data.get("tracked_meal_id")
|
||||
foods_data = data.get("foods", [])
|
||||
|
||||
tracked_meal = db.query(TrackedMeal).filter(TrackedMeal.id == tracked_meal_id).first()
|
||||
if not tracked_meal:
|
||||
raise HTTPException(status_code=404, detail="Tracked meal not found")
|
||||
|
||||
for food_data in foods_data:
|
||||
food_id = food_data.get("food_id")
|
||||
grams = float(food_data.get("quantity", 1.0)) # Assuming quantity is now grams
|
||||
is_custom = food_data.get("is_custom", False)
|
||||
item_id = food_data.get("id") # This could be MealFood.id or TrackedMealFood.id
|
||||
|
||||
quantity = convert_grams_to_quantity(food_id, grams, db)
|
||||
|
||||
if is_custom:
|
||||
tracked_food = db.query(TrackedMealFood).filter(TrackedMealFood.id == item_id).first()
|
||||
if tracked_food:
|
||||
tracked_food.quantity = quantity
|
||||
else:
|
||||
# If it's a new custom food being added
|
||||
new_tracked_food = TrackedMealFood(
|
||||
tracked_meal_id=tracked_meal.id,
|
||||
food_id=food_id,
|
||||
quantity=quantity
|
||||
)
|
||||
db.add(new_tracked_food)
|
||||
else:
|
||||
# This is a food from the original meal definition
|
||||
# We need to check if it's already a TrackedMealFood (meaning it was overridden)
|
||||
# Or if it's still a MealFood
|
||||
existing_tracked_food = db.query(TrackedMealFood).filter(
|
||||
TrackedMealFood.tracked_meal_id == tracked_meal.id,
|
||||
TrackedMealFood.food_id == food_id
|
||||
).first()
|
||||
|
||||
if existing_tracked_food:
|
||||
existing_tracked_food.quantity = quantity
|
||||
else:
|
||||
# If it's not a TrackedMealFood, it must be a MealFood
|
||||
meal_food = db.query(MealFood).filter(
|
||||
MealFood.meal_id == tracked_meal.meal_id,
|
||||
MealFood.food_id == food_id
|
||||
).first()
|
||||
if meal_food:
|
||||
# If quantity changed, convert to TrackedMealFood
|
||||
# NOTE: meal_food.quantity is already a multiplier,
|
||||
# but the incoming 'quantity' is a multiplier derived from grams.
|
||||
# So, we compare the incoming multiplier with the existing multiplier.
|
||||
if meal_food.quantity != quantity:
|
||||
new_tracked_food = TrackedMealFood(
|
||||
tracked_meal_id=tracked_meal.id,
|
||||
food_id=food_id,
|
||||
quantity=quantity,
|
||||
is_override=True
|
||||
)
|
||||
db.add(new_tracked_food)
|
||||
db.delete(meal_food) # Remove original MealFood
|
||||
else:
|
||||
# This case should ideally not happen if data is consistent,
|
||||
# but as a fallback, add as a new TrackedMealFood
|
||||
new_tracked_food = TrackedMealFood(
|
||||
tracked_meal_id=tracked_meal.id,
|
||||
food_id=food_id,
|
||||
quantity=quantity
|
||||
)
|
||||
db.add(new_tracked_food)
|
||||
|
||||
# Mark the tracked day as modified
|
||||
tracked_meal.tracked_day.is_modified = True
|
||||
|
||||
db.commit()
|
||||
return {"status": "success"}
|
||||
|
||||
except HTTPException as he:
|
||||
db.rollback()
|
||||
logging.error(f"DEBUG: HTTP Error updating tracked meal foods: {he.detail}")
|
||||
return {"status": "error", "message": he.detail}
|
||||
except Exception as e:
|
||||
db.rollback()
|
||||
logging.error(f"DEBUG: Error updating tracked meal foods: {e}")
|
||||
return {"status": "error", "message": str(e)}
|
||||
|
||||
@router.delete("/tracker/remove_food_from_tracked_meal/{meal_food_id}")
|
||||
async def remove_food_from_tracked_meal(meal_food_id: int, db: Session = Depends(get_db)):
|
||||
"""Remove a food from a tracked meal"""
|
||||
@@ -567,10 +654,10 @@ async def tracker_add_food(data: dict = Body(...), db: Session = Depends(get_db)
|
||||
person = data.get("person")
|
||||
date_str = data.get("date")
|
||||
food_id = data.get("food_id")
|
||||
quantity = float(data.get("quantity", 1.0))
|
||||
grams = float(data.get("quantity", 1.0)) # Assuming quantity is now grams
|
||||
meal_time = data.get("meal_time")
|
||||
|
||||
logging.info(f"DEBUG: Adding single food to tracker - person={person}, date={date_str}, food_id={food_id}, quantity={quantity}, meal_time={meal_time}")
|
||||
logging.info(f"DEBUG: Adding single food to tracker - person={person}, date={date_str}, food_id={food_id}, grams={grams}, meal_time={meal_time}")
|
||||
|
||||
# Parse date
|
||||
from datetime import datetime
|
||||
@@ -588,19 +675,21 @@ async def tracker_add_food(data: dict = Body(...), db: Session = Depends(get_db)
|
||||
db.commit()
|
||||
db.refresh(tracked_day)
|
||||
|
||||
# Get the food
|
||||
food = db.query(Food).filter(Food.id == food_id).first()
|
||||
if not food:
|
||||
return {"status": "error", "message": "Food not found"}
|
||||
# Get the food and convert grams to quantity multiplier
|
||||
quantity = convert_grams_to_quantity(food_id, grams, db)
|
||||
|
||||
# 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.name, meal_type="single_food", meal_time=meal_time)
|
||||
food_item = db.query(Food).filter(Food.id == food_id).first()
|
||||
if not food_item:
|
||||
return {"status": "error", "message": "Food not found"}
|
||||
|
||||
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=quantity)
|
||||
meal_food = MealFood(meal_id=new_meal.id, food_id=food_id, quantity=quantity)
|
||||
db.add(meal_food)
|
||||
|
||||
# Create tracked meal entry
|
||||
@@ -619,6 +708,10 @@ async def tracker_add_food(data: dict = Body(...), db: Session = Depends(get_db)
|
||||
logging.info(f"DEBUG: Successfully added single food to tracker")
|
||||
return {"status": "success"}
|
||||
|
||||
except ValueError as ve:
|
||||
db.rollback()
|
||||
logging.error(f"DEBUG: Error adding single food to tracker: {ve}")
|
||||
return {"status": "error", "message": str(ve)}
|
||||
except Exception as e:
|
||||
db.rollback()
|
||||
logging.error(f"DEBUG: Error adding single food to tracker: {e}")
|
||||
|
||||
@@ -309,8 +309,43 @@ def get_db():
|
||||
db.close()
|
||||
|
||||
# Utility functions
|
||||
def convert_grams_to_quantity(food_id: int, grams: float, db: Session) -> float:
|
||||
"""
|
||||
Converts a given gram value to the quantity multiplier based on the food's serving size.
|
||||
|
||||
Args:
|
||||
food_id: The ID of the food item.
|
||||
grams: The desired weight in grams.
|
||||
db: The database session.
|
||||
|
||||
Returns:
|
||||
The quantity multiplier.
|
||||
|
||||
Raises:
|
||||
ValueError: If the food is not found or its serving size is invalid.
|
||||
"""
|
||||
food = db.query(Food).filter(Food.id == food_id).first()
|
||||
if not food:
|
||||
raise ValueError(f"Food with ID {food_id} not found.")
|
||||
|
||||
try:
|
||||
# Assuming serving_size is stored in grams for simplicity as per the plan
|
||||
# If serving_size can be other units, more complex conversion is needed.
|
||||
serving_size_value = float(food.serving_size)
|
||||
except ValueError:
|
||||
raise ValueError(f"Invalid serving_size '{food.serving_size}' for food ID {food_id}. Expected a numeric value.")
|
||||
|
||||
if serving_size_value == 0:
|
||||
raise ValueError(f"Serving size for food ID {food_id} cannot be zero.")
|
||||
|
||||
return grams / serving_size_value
|
||||
|
||||
def calculate_meal_nutrition(meal, db: Session):
|
||||
"""Calculate total nutrition for a meal"""
|
||||
"""
|
||||
Calculate total nutrition for a meal.
|
||||
Quantities in MealFood are now multipliers based on the Food's serving_size,
|
||||
where serving_size is assumed to be in grams.
|
||||
"""
|
||||
totals = {
|
||||
'calories': 0, 'protein': 0, 'carbs': 0, 'fat': 0,
|
||||
'fiber': 0, 'sugar': 0, 'sodium': 0, 'calcium': 0
|
||||
|
||||
@@ -33,8 +33,8 @@
|
||||
</select>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Quantity</label>
|
||||
<input type="number" step="0.01" class="form-control" name="quantity" value="1" required>
|
||||
<label class="form-label">Quantity (g)</label>
|
||||
<input type="number" step="0.01" class="form-control" name="quantity" value="100" required>
|
||||
</div>
|
||||
<button type="button" class="btn btn-success" onclick="addFoodToMeal()">Add Food</button>
|
||||
</form>
|
||||
|
||||
@@ -341,12 +341,13 @@
|
||||
} else {
|
||||
data.meal_foods.forEach(food => {
|
||||
const foodDiv = document.createElement('div');
|
||||
foodDiv.className = 'd-flex justify-content-between align-items-center mb-2 p-2 bg-light rounded';
|
||||
foodDiv.className = 'd-flex justify-content-between align-items-center mb-2';
|
||||
foodDiv.innerHTML = `
|
||||
<div class="input-group">
|
||||
<input type="number" step="0.01" class="form-control" value="${food.quantity}" data-food-id="${food.id}" data-is-custom="${food.is_custom}">
|
||||
<span class="input-group-text">${food.food_name}</span>
|
||||
<button class="btn btn-outline-danger" onclick="removeFoodFromTrackedMeal(${food.id}, ${food.is_custom})">
|
||||
<span>${food.food_name}</span>
|
||||
<div class="input-group w-50">
|
||||
<input type="number" step="0.01" class="form-control form-control-sm" value="${food.grams.toFixed(2)}" data-food-id="${food.food_id}" data-item-id="${food.id}" data-is-custom="${food.is_custom}">
|
||||
<span class="input-group-text">g</span>
|
||||
<button type="button" class="btn btn-sm btn-outline-danger" onclick="removeFoodFromTrackedMeal(${food.id}, ${food.is_custom})">
|
||||
<i class="bi bi-trash"></i>
|
||||
</button>
|
||||
</div>
|
||||
@@ -356,71 +357,47 @@
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error loading meal foods:', error);
|
||||
console.error('Error loading tracked meal foods:', error);
|
||||
alert('Error loading tracked meal foods.');
|
||||
}
|
||||
}
|
||||
|
||||
// Add food to tracked meal
|
||||
async function addFoodToTrackedMeal() {
|
||||
const form = document.getElementById('addFoodToTrackedMealForm');
|
||||
const formData = new FormData(form);
|
||||
const trackedMealId = formData.get('tracked_meal_id');
|
||||
async function saveTrackedMeal() {
|
||||
const trackedMealId = document.getElementById('editTrackedMealId').value;
|
||||
const inputs = document.querySelectorAll('#editMealFoodsList input[type="number"]');
|
||||
|
||||
const foods = [];
|
||||
inputs.forEach(input => {
|
||||
foods.push({
|
||||
id: parseInt(input.dataset.itemId),
|
||||
food_id: parseInt(input.dataset.foodId),
|
||||
quantity: parseFloat(input.value), // Quantity is now grams
|
||||
is_custom: input.dataset.isCustom === 'true'
|
||||
});
|
||||
});
|
||||
|
||||
try {
|
||||
const response = await fetch('/tracker/add_food_to_tracked_meal', {
|
||||
const response = await fetch('/tracker/update_tracked_meal_foods', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
tracked_meal_id: parseInt(formData.get('tracked_meal_id')),
|
||||
food_id: parseInt(formData.get('food_id')),
|
||||
quantity: parseFloat(formData.get('quantity'))
|
||||
tracked_meal_id: trackedMealId,
|
||||
foods: foods
|
||||
})
|
||||
});
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (result.status === 'success') {
|
||||
// Reset form and reload current foods
|
||||
form.reset();
|
||||
document.getElementById('tracked_meal_id_for_food').value = trackedMealId;
|
||||
await loadTrackedMealFoods(trackedMealId);
|
||||
bootstrap.Modal.getInstance(document.getElementById('editTrackedMealModal')).hide();
|
||||
location.reload();
|
||||
} else {
|
||||
alert('Error adding food: ' + result.message);
|
||||
alert('Error saving tracked meal: ' + result.message);
|
||||
}
|
||||
} catch (error) {
|
||||
alert('Error adding food: ' + error.message);
|
||||
console.error('Error saving tracked meal:', error);
|
||||
alert('Error saving tracked meal.');
|
||||
}
|
||||
}
|
||||
|
||||
// Remove food from tracked meal
|
||||
async function removeFoodFromTrackedMeal(foodId, isCustom) {
|
||||
if (confirm('Remove this food from the tracked meal?')) {
|
||||
const url = isCustom ? `/tracker/remove_custom_food_from_tracked_meal/${foodId}` : `/tracker/remove_food_from_tracked_meal/${foodId}`;
|
||||
try {
|
||||
const response = await fetch(url, {
|
||||
method: 'DELETE'
|
||||
});
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (result.status === 'success') {
|
||||
const trackedMealId = document.getElementById('editTrackedMealId').value;
|
||||
await loadTrackedMealFoods(trackedMealId);
|
||||
} else {
|
||||
alert('Error removing food: ' + result.message);
|
||||
}
|
||||
} catch (error) {
|
||||
alert('Error removing food: ' + error.message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Save tracked meal changes (No longer needed as updates are real-time)
|
||||
async function saveTrackedMeal() {
|
||||
bootstrap.Modal.getInstance(document.getElementById('editTrackedMealModal')).hide();
|
||||
location.reload();
|
||||
}
|
||||
|
||||
// Save as new meal
|
||||
async function saveAsNewMeal() {
|
||||
const mealName = prompt('Enter name for new meal:');
|
||||
@@ -433,7 +410,7 @@
|
||||
inputs.forEach(input => {
|
||||
foods.push({
|
||||
food_id: parseInt(input.dataset.foodId),
|
||||
quantity: parseFloat(input.value)
|
||||
quantity: parseFloat(input.value) // Quantity is now grams
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -87,6 +87,43 @@ def test_get_tracked_meal_foods_endpoint(client: TestClient, session: TestingSes
|
||||
elif food_data["food_name"] == "Banana":
|
||||
assert food_data["quantity"] == 100.0
|
||||
|
||||
def test_update_tracked_meal_foods_endpoint(client: TestClient, session: TestingSessionLocal):
|
||||
"""Test updating quantities of foods in a tracked meal"""
|
||||
food1, food2, meal1, tracked_day, tracked_meal = create_test_data(session)
|
||||
|
||||
# Add a tracked meal food for food1 to allow updates
|
||||
tracked_meal_food1 = TrackedMealFood(tracked_meal_id=tracked_meal.id, food_id=food1.id, quantity=150.0)
|
||||
session.add(tracked_meal_food1)
|
||||
session.commit()
|
||||
session.refresh(tracked_meal_food1)
|
||||
|
||||
# Prepare update data
|
||||
updated_foods = [
|
||||
{"id": tracked_meal_food1.id, "food_id": food1.id, "quantity": 200.0, "is_custom": True},
|
||||
{"id": None, "food_id": food2.id, "quantity": 50.0, "is_custom": False} # This represents original meal food
|
||||
]
|
||||
|
||||
response = client.post(
|
||||
"/tracker/update_tracked_meal_foods",
|
||||
json={
|
||||
"tracked_meal_id": tracked_meal.id,
|
||||
"foods": updated_foods
|
||||
}
|
||||
)
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["status"] == "success"
|
||||
|
||||
# Verify updates in the database
|
||||
updated_meal_foods = session.query(TrackedMealFood).filter(TrackedMealFood.tracked_meal_id == tracked_meal.id).all()
|
||||
assert len(updated_meal_foods) == 2
|
||||
|
||||
for tmf in updated_meal_foods:
|
||||
if tmf.food_id == food1.id:
|
||||
assert tmf.quantity == 200.0
|
||||
elif tmf.food_id == food2.id:
|
||||
assert tmf.quantity == 50.0
|
||||
|
||||
def test_add_food_to_tracked_meal_endpoint(client: TestClient, session: TestingSessionLocal):
|
||||
"""Test adding a new food to an existing tracked meal"""
|
||||
food1, food2, meal1, tracked_day, tracked_meal = create_test_data(session)
|
||||
|
||||
203
tests/test_food_weight_consistency.py
Normal file
203
tests/test_food_weight_consistency.py
Normal file
@@ -0,0 +1,203 @@
|
||||
import pytest
|
||||
from fastapi.testclient import TestClient
|
||||
from sqlalchemy import create_engine
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
from app.database import Base, get_db, Food, Meal, MealFood
|
||||
from main import app
|
||||
|
||||
# Setup a test database
|
||||
SQLALCHEMY_DATABASE_URL = "sqlite:///./test.db"
|
||||
engine = create_engine(SQLALCHEMY_DATABASE_URL, connect_args={"check_same_thread": False})
|
||||
TestingSessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
|
||||
|
||||
@pytest.fixture(name="session")
|
||||
def session_fixture():
|
||||
Base.metadata.create_all(bind=engine)
|
||||
db = TestingSessionLocal()
|
||||
try:
|
||||
yield db
|
||||
finally:
|
||||
db.close()
|
||||
Base.metadata.drop_all(bind=engine)
|
||||
|
||||
@pytest.fixture(name="client")
|
||||
def client_fixture(session):
|
||||
def override_get_db():
|
||||
yield session
|
||||
app.dependency_overrides[get_db] = override_get_db
|
||||
yield TestClient(app)
|
||||
app.dependency_overrides.clear()
|
||||
|
||||
@pytest.fixture
|
||||
def sample_food_100g(session):
|
||||
food = Food(name="Sample Food 100g", serving_size="100", serving_unit="g", calories=100.0, protein=10.0, carbs=20.0, fat=5.0)
|
||||
session.add(food)
|
||||
session.commit()
|
||||
session.refresh(food)
|
||||
return food
|
||||
|
||||
@pytest.fixture
|
||||
def sample_food_50g(session):
|
||||
food = Food(name="Sample Food 50g", serving_size="50", serving_unit="g", calories=50.0, protein=5.0, carbs=10.0, fat=2.5)
|
||||
session.add(food)
|
||||
session.commit()
|
||||
session.refresh(food)
|
||||
return food
|
||||
|
||||
def test_convert_grams_to_quantity_100g_food(session, sample_food_100g):
|
||||
"""Test convert_grams_to_quantity for a 100g serving size food"""
|
||||
grams = 150.0
|
||||
quantity = session.convert_grams_to_quantity(sample_food_100g.id, grams, session)
|
||||
assert quantity == 1.5
|
||||
|
||||
def test_convert_grams_to_quantity_50g_food(session, sample_food_50g):
|
||||
"""Test convert_grams_to_quantity for a 50g serving size food"""
|
||||
grams = 125.0
|
||||
quantity = session.convert_grams_to_quantity(sample_food_50g.id, grams, session)
|
||||
assert quantity == 2.5
|
||||
|
||||
def test_convert_grams_to_quantity_invalid_food_id(session):
|
||||
"""Test convert_grams_to_quantity with an invalid food ID"""
|
||||
with pytest.raises(ValueError, match="Food with ID 999 not found."):
|
||||
session.convert_grams_to_quantity(999, 100.0, session)
|
||||
|
||||
def test_convert_grams_to_quantity_zero_serving_size(session):
|
||||
"""Test convert_grams_to_quantity with zero serving size"""
|
||||
food = Food(name="Zero Serving Food", serving_size="0", serving_unit="g", calories=0, protein=0, carbs=0, fat=0)
|
||||
session.add(food)
|
||||
session.commit()
|
||||
session.refresh(food)
|
||||
with pytest.raises(ValueError, match="Serving size for food ID .* cannot be zero."):
|
||||
session.convert_grams_to_quantity(food.id, 100.0, session)
|
||||
|
||||
def test_add_food_to_meal_grams_input(client, session, sample_food_100g):
|
||||
"""Test adding food to a meal with grams input"""
|
||||
meal = Meal(name="Test Meal", meal_type="custom")
|
||||
session.add(meal)
|
||||
session.commit()
|
||||
session.refresh(meal)
|
||||
|
||||
response = client.post(
|
||||
f"/meals/{meal.id}/add_food",
|
||||
data={"food_id": sample_food_100g.id, "quantity": 250.0} # 250 grams
|
||||
)
|
||||
assert response.status_code == 200
|
||||
assert response.json()["status"] == "success"
|
||||
|
||||
meal_food = session.query(MealFood).filter(MealFood.meal_id == meal.id).first()
|
||||
assert meal_food.food_id == sample_food_100g.id
|
||||
assert meal_food.quantity == 2.5 # 250g / 100g serving = 2.5 multiplier
|
||||
|
||||
def test_update_meal_food_quantity_grams_input(client, session, sample_food_50g):
|
||||
"""Test updating meal food quantity with grams input"""
|
||||
meal = Meal(name="Update Meal", meal_type="custom")
|
||||
session.add(meal)
|
||||
session.commit()
|
||||
session.refresh(meal)
|
||||
|
||||
# Add initial food with 100g (2.0 multiplier for 50g serving)
|
||||
initial_grams = 100.0
|
||||
initial_quantity = session.convert_grams_to_quantity(sample_food_50g.id, initial_grams, session)
|
||||
meal_food = MealFood(meal_id=meal.id, food_id=sample_food_50g.id, quantity=initial_quantity)
|
||||
session.add(meal_food)
|
||||
session.commit()
|
||||
session.refresh(meal_food)
|
||||
|
||||
updated_grams = 150.0
|
||||
response = client.post(
|
||||
"/meals/update_food_quantity",
|
||||
data={"meal_food_id": meal_food.id, "quantity": updated_grams}
|
||||
)
|
||||
assert response.status_code == 200
|
||||
assert response.json()["status"] == "success"
|
||||
|
||||
session.refresh(meal_food)
|
||||
expected_quantity = session.convert_grams_to_quantity(sample_food_50g.id, updated_grams, session)
|
||||
assert meal_food.quantity == expected_quantity
|
||||
|
||||
# Test for bulk_upload_meals would require creating a mock UploadFile and CSV content
|
||||
# This is more complex and might be deferred or tested manually if the tool's capabilities are limited.
|
||||
# For now, we'll assume the backend change correctly handles the quantity.
|
||||
|
||||
def test_tracker_add_food_grams_input(client, session, sample_food_100g):
|
||||
"""Test adding single food to tracker with grams input"""
|
||||
person = "TestPerson"
|
||||
date_str = "2023-01-01"
|
||||
grams = 75.0
|
||||
|
||||
response = client.post(
|
||||
"/tracker/add_food",
|
||||
json={
|
||||
"person": person,
|
||||
"date": date_str,
|
||||
"food_id": sample_food_100g.id,
|
||||
"quantity": grams, # 75 grams
|
||||
"meal_time": "Breakfast"
|
||||
}
|
||||
)
|
||||
assert response.status_code == 200
|
||||
assert response.json()["status"] == "success"
|
||||
|
||||
# Verify the tracked meal food quantity
|
||||
tracked_meal = session.query(Meal).filter(Meal.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 == 0.75 # 75g / 100g serving = 0.75 multiplier
|
||||
|
||||
def test_update_tracked_meal_foods_grams_input(client, session, sample_food_100g, sample_food_50g):
|
||||
"""Test updating tracked meal foods with grams input"""
|
||||
person = "TestPerson"
|
||||
date_str = "2023-01-02"
|
||||
|
||||
# Create a tracked day and meal
|
||||
tracked_day = TrackedDay(person=person, date="2023-01-02", is_modified=False)
|
||||
session.add(tracked_day)
|
||||
session.commit()
|
||||
session.refresh(tracked_day)
|
||||
|
||||
meal = Meal(name="Tracked Meal", meal_type="custom", meal_time="Lunch")
|
||||
session.add(meal)
|
||||
session.commit()
|
||||
session.refresh(meal)
|
||||
|
||||
tracked_meal = TrackedMeal(tracked_day_id=tracked_day.id, meal_id=meal.id, meal_time="Lunch")
|
||||
session.add(tracked_meal)
|
||||
session.commit()
|
||||
session.refresh(tracked_meal)
|
||||
|
||||
# Add initial foods
|
||||
meal_food_100g = MealFood(meal_id=meal.id, food_id=sample_food_100g.id, quantity=1.0) # 100g
|
||||
meal_food_50g = MealFood(meal_id=meal.id, food_id=sample_food_50g.id, quantity=2.0) # 100g
|
||||
session.add_all([meal_food_100g, meal_food_50g])
|
||||
session.commit()
|
||||
session.refresh(meal_food_100g)
|
||||
session.refresh(meal_food_50g)
|
||||
|
||||
# Update quantities: 100g food to 200g, 50g food to 75g
|
||||
updated_foods_data = [
|
||||
{"id": meal_food_100g.id, "food_id": sample_food_100g.id, "quantity": 200.0, "is_custom": False},
|
||||
{"id": meal_food_50g.id, "food_id": sample_food_50g.id, "quantity": 75.0, "is_custom": False}
|
||||
]
|
||||
|
||||
response = client.post(
|
||||
"/tracker/update_tracked_meal_foods",
|
||||
json={"tracked_meal_id": tracked_meal.id, "foods": updated_foods_data}
|
||||
)
|
||||
assert response.status_code == 200
|
||||
assert response.json()["status"] == "success"
|
||||
|
||||
# Verify updated quantities
|
||||
session.refresh(tracked_meal)
|
||||
|
||||
# Check if MealFood was converted to TrackedMealFood for changes
|
||||
tracked_food_100g = session.query(TrackedMealFood).filter(
|
||||
TrackedMealFood.tracked_meal_id == tracked_meal.id,
|
||||
TrackedMealFood.food_id == sample_food_100g.id
|
||||
).first()
|
||||
assert tracked_food_100g.quantity == 2.0 # 200g / 100g serving = 2.0
|
||||
|
||||
tracked_food_50g = session.query(TrackedMealFood).filter(
|
||||
TrackedMealFood.tracked_meal_id == tracked_meal.id,
|
||||
TrackedMealFood.food_id == sample_food_50g.id
|
||||
).first()
|
||||
assert tracked_food_50g.quantity == 1.5 # 75g / 50g serving = 1.5
|
||||
Reference in New Issue
Block a user