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 sqlalchemy import create_engine, Column, Integer, String, Float, DateTime, ForeignKey, Text, Date, Boolean
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 typing import List, Optional
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 {})
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
from sqlalchemy.orm import declarative_base
from sqlalchemy.orm import declarative_base
Base = declarative_base()
# Initialize FastAPI app
@@ -1350,11 +1349,10 @@ async def bulk_upload_meals(file: UploadFile = File(...), db: Session = Depends(
stats = {'created': 0, 'updated': 0, 'errors': []}
# Skip header rows
next(reader) # First header
next(reader) # Second header
# Skip header
header = next(reader)
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:
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()
if not food:
logging.error(f"Food '{food_name}' not found in database.")
# Get all food names for debugging
all_foods = db.query(Food.name).limit(10).all()
food_names = [f[0] for f in all_foods]
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))
# Create/update meal
@@ -2206,11 +2206,17 @@ async def create_template(request: Request, db: Session = Depends(get_db)):
# Process meal assignments
if meal_assignments_str:
logging.info(f"Processing meal assignments: {meal_assignments_str}")
assignments = meal_assignments_str.split(',')
for assignment in assignments:
meal_time, meal_id_str = assignment.split(':')
meal_id = int(meal_id_str)
meal_time, meal_id_str = assignment.split(':', 1)
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()
if meal:
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