sync - build workin

This commit is contained in:
2025-09-29 10:01:19 -07:00
parent 989576e7b2
commit c91b01665d
3 changed files with 167 additions and 8 deletions

22
main.py
View File

@@ -12,7 +12,7 @@ from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import HTMLResponse from fastapi.responses import HTMLResponse
from sqlalchemy import create_engine, Column, Integer, String, Float, DateTime, ForeignKey, Text, Date, Boolean from sqlalchemy import create_engine, Column, Integer, String, Float, DateTime, ForeignKey, Text, Date, Boolean
from sqlalchemy import or_ from sqlalchemy import or_
from sqlalchemy.orm import sessionmaker, Session, relationship from sqlalchemy.orm import sessionmaker, Session, relationship, declarative_base
from pydantic import BaseModel, ConfigDict from pydantic import BaseModel, ConfigDict
from typing import List, Optional from typing import List, Optional
from datetime import date, datetime from datetime import date, datetime
@@ -54,7 +54,6 @@ except Exception as e:
engine = create_engine(DATABASE_URL, connect_args={"check_same_thread": False} if "sqlite" in DATABASE_URL else {}) engine = create_engine(DATABASE_URL, connect_args={"check_same_thread": False} if "sqlite" in DATABASE_URL else {})
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
from sqlalchemy.orm import declarative_base from sqlalchemy.orm import declarative_base
from sqlalchemy.orm import declarative_base
Base = declarative_base() Base = declarative_base()
# Initialize FastAPI app # Initialize FastAPI app
@@ -1350,11 +1349,10 @@ async def bulk_upload_meals(file: UploadFile = File(...), db: Session = Depends(
stats = {'created': 0, 'updated': 0, 'errors': []} stats = {'created': 0, 'updated': 0, 'errors': []}
# Skip header rows # Skip header
next(reader) # First header header = next(reader)
next(reader) # Second header
for row_num, row in enumerate(reader, 3): # Start at row 3 for row_num, row in enumerate(reader, 2): # Start at row 2
if not row: if not row:
continue continue
@@ -1387,10 +1385,12 @@ async def bulk_upload_meals(file: UploadFile = File(...), db: Session = Depends(
food = db.query(Food).filter(Food.name.ilike(f"%{search_pattern}%")).first() food = db.query(Food).filter(Food.name.ilike(f"%{search_pattern}%")).first()
if not food: if not food:
logging.error(f"Food '{food_name}' not found in database.")
# Get all food names for debugging # Get all food names for debugging
all_foods = db.query(Food.name).limit(10).all() all_foods = db.query(Food.name).limit(10).all()
food_names = [f[0] for f in all_foods] food_names = [f[0] for f in all_foods]
raise ValueError(f"Food '{food_name}' not found. Available foods include: {', '.join(food_names[:5])}...") raise ValueError(f"Food '{food_name}' not found. Available foods include: {', '.join(food_names[:5])}...")
logging.info(f"Found food '{food_name}' with id {food.id}")
ingredients.append((food.id, quantity)) ingredients.append((food.id, quantity))
# Create/update meal # Create/update meal
@@ -2206,11 +2206,17 @@ async def create_template(request: Request, db: Session = Depends(get_db)):
# Process meal assignments # Process meal assignments
if meal_assignments_str: if meal_assignments_str:
logging.info(f"Processing meal assignments: {meal_assignments_str}")
assignments = meal_assignments_str.split(',') assignments = meal_assignments_str.split(',')
for assignment in assignments: for assignment in assignments:
meal_time, meal_id_str = assignment.split(':') meal_time, meal_id_str = assignment.split(':', 1)
meal_id = int(meal_id_str) logging.info(f"Processing assignment: meal_time='{meal_time}', meal_id_str='{meal_id_str}'")
if not meal_id_str:
logging.warning(f"Skipping empty meal ID for meal_time '{meal_time}'")
continue
meal_id = int(meal_id_str)
meal = db.query(Meal).filter(Meal.id == meal_id).first() meal = db.query(Meal).filter(Meal.id == meal_id).first()
if meal: if meal:
template_meal = TemplateMeal( template_meal = TemplateMeal(

50
plan.md Normal file
View File

@@ -0,0 +1,50 @@
# Plan for Pytest of Details Tab
This plan outlines the steps to create a comprehensive pytest for the "details" tab in the Food Planner application.
## Objective
The goal is to create a suite of tests that verify the functionality of the `/detailed` route, ensuring it correctly handles both plan-based and template-based views, as well as invalid inputs.
## File to be Created
- `tests/test_detailed.py`
## Test Cases
### 1. Test with `plan_date`
- **Description**: This test will check the `/detailed` route when a valid `plan_date` is provided.
- **Steps**:
1. Create mock data: a `Food`, a `Meal`, a `MealFood`, and a `Plan` for a specific date.
2. Send a GET request to `/detailed` with the `person` and `plan_date` as query parameters.
3. Assert that the response status code is 200.
4. Assert that the response contains the correct data for the plan.
### 2. Test with `template_id`
- **Description**: This test will check the `/detailed` route when a valid `template_id` is provided.
- **Steps**:
1. Create mock data: a `Food`, a `Meal`, a `Template`, and a `TemplateMeal`.
2. Send a GET request to `/detailed` with the `template_id` as a query parameter.
3. Assert that the response status code is 200.
4. Assert that the response contains the correct data for the template.
### 3. Test with Invalid `plan_date`
- **Description**: This test will ensure the route handles an invalid `plan_date` gracefully.
- **Steps**:
1. Send a GET request to `/detailed` with a non-existent `plan_date`.
2. Assert that the response status code is 200 (as the page should still render).
3. Assert that the response contains a message indicating that no plan was found.
### 4. Test with Invalid `template_id`
- **Description**: This test will ensure the route handles an invalid `template_id` gracefully.
- **Steps**:
1. Send a GET request to `/detailed` with a non-existent `template_id`.
2. Assert that the response status code is 200.
3. Assert that the response contains a message indicating that the template was not found.
## Implementation Details
The `tests/test_detailed.py` file should include:
- Imports for `pytest`, `TestClient`, and the necessary models from `main.py`.
- A `TestClient` instance for making requests to the application.
- Fixtures to set up and tear down the test database for each test function to ensure test isolation.
This plan provides a clear path for a developer to implement the required tests.

103
tests/test_detailed.py Normal file
View File

@@ -0,0 +1,103 @@
import pytest
from fastapi.testclient import TestClient
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from main import app, get_db, Base, Food, Meal, MealFood, Plan, Template, TemplateMeal
from datetime import date, timedelta
# Setup test database
SQLALCHEMY_DATABASE_URL = "sqlite:///./test_detailed.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_detailed_page_no_params(client):
response = client.get("/detailed")
assert response.status_code == 200
assert "error" in response.text or "Template Not Found" in response.text # Based on the existing code, it returns an error template if neither plan_date nor template_id is provided, and template_id is None.
def test_detailed_page_with_plan_date(client, session):
# Create mock data
food = Food(name="Apple", serving_size="100", serving_unit="g", calories=52, protein=0.3, carbs=14, fat=0.2)
session.add(food)
session.commit()
session.refresh(food)
meal = Meal(name="Fruit Snack", meal_type="snack", meal_time="Snack")
session.add(meal)
session.commit()
session.refresh(meal)
meal_food = MealFood(meal_id=meal.id, food_id=food.id, quantity=1.0)
session.add(meal_food)
session.commit()
test_date = date.today()
plan = Plan(person="Sarah", date=test_date, meal_id=meal.id, meal_time="Snack")
session.add(plan)
session.commit()
response = client.get(f"/detailed?person=Sarah&plan_date={test_date.isoformat()}")
assert response.status_code == 200
assert b"Sarah's Detailed Plan" in response.content
assert b"Fruit Snack" in response.content
def test_detailed_page_with_template_id(client, session):
# Create mock data
food = Food(name="Banana", serving_size="100", serving_unit="g", calories=89, protein=1.1, carbs=23, fat=0.3)
session.add(food)
session.commit()
session.refresh(food)
meal = Meal(name="Banana Smoothie", meal_type="breakfast", meal_time="Breakfast")
session.add(meal)
session.commit()
session.refresh(meal)
meal_food = MealFood(meal_id=meal.id, food_id=food.id, quantity=1.0)
session.add(meal_food)
session.commit()
template = Template(name="Morning Boost")
session.add(template)
session.commit()
session.refresh(template)
template_meal = TemplateMeal(template_id=template.id, meal_id=meal.id, meal_time="Breakfast")
session.add(template_meal)
session.commit()
response = client.get(f"/detailed?template_id={template.id}")
assert response.status_code == 200
assert b"Morning Boost Template" in response.content
assert b"Banana Smoothie" in response.content
def test_detailed_page_with_invalid_plan_date(client):
invalid_date = date.today() + timedelta(days=100) # A date far in the future
response = client.get(f"/detailed?person=Sarah&plan_date={invalid_date.isoformat()}")
assert response.status_code == 200
assert b"Sarah's Detailed Plan" in response.content
assert b"No meals planned for this day." in response.content # Assuming this message is displayed
def test_detailed_page_with_invalid_template_id(client):
response = client.get(f"/detailed?template_id=99999")
assert response.status_code == 200
assert b"Template Not Found" in response.content