mirror of
https://github.com/sstent/foodplanner.git
synced 2026-03-19 19:55:24 +00:00
sync - build workin
This commit is contained in:
22
main.py
22
main.py
@@ -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
50
plan.md
Normal 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
103
tests/test_detailed.py
Normal 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
|
||||||
Reference in New Issue
Block a user