From d034c7349d151c4a3ee04e93d121aee8ba2fa4c7 Mon Sep 17 00:00:00 2001 From: sstent Date: Mon, 29 Sep 2025 17:27:59 -0700 Subject: [PATCH] refactored --- TDD_RULES.md | 29 -------- migrate_db_schema.py | 73 -------------------- migrate_food_brand.py | 61 ----------------- migrate_to_dates.py | 128 ------------------------------------ migrate_tracker_modified.py | 52 --------------- migrate_tracker_schema.py | 50 -------------- plan.md | 92 -------------------------- 7 files changed, 485 deletions(-) delete mode 100644 TDD_RULES.md delete mode 100644 migrate_db_schema.py delete mode 100644 migrate_food_brand.py delete mode 100644 migrate_to_dates.py delete mode 100644 migrate_tracker_modified.py delete mode 100644 migrate_tracker_schema.py delete mode 100644 plan.md diff --git a/TDD_RULES.md b/TDD_RULES.md deleted file mode 100644 index ae2d11a..0000000 --- a/TDD_RULES.md +++ /dev/null @@ -1,29 +0,0 @@ -# TDD Development Rules - -This document outlines the rules for Test-Driven Development (TDD) using pytest within our containerized environment. - -## Principles - -- **Test-First Approach**: All new features must begin with writing a failing test. -- **Debug-Driven Tests**: When a bug is identified during debugging, a new pytest must be created to reproduce the bug and validate its fix. - -## Pytest Execution - -Pytest should be run within the Docker Compose environment to ensure consistency and isolation. - -### Running Specific Tests - -To run tests for a specific file: - -```bash -docker compose build -docker compose run --remove-orphans foodtracker pytest tests/ -``` - -### Running All Tests - -To run all tests in the project: - -```bash -docker compose build -docker compose run --remove-orphans foodtracker pytest \ No newline at end of file diff --git a/migrate_db_schema.py b/migrate_db_schema.py deleted file mode 100644 index 7794e06..0000000 --- a/migrate_db_schema.py +++ /dev/null @@ -1,73 +0,0 @@ -#!/usr/bin/env python3 -""" -Database migration script to add new fields for food sources and meal times. -Run this script to update the database schema. -""" - -import sqlite3 -from sqlalchemy import create_engine, text -from main import Base, engine - -def migrate_database(): - """Add new columns to existing tables""" - - # Connect to database - conn = sqlite3.connect('./meal_planner.db') - cursor = conn.cursor() - - try: - # Check if source column exists in foods table - cursor.execute("PRAGMA table_info(foods)") - columns = cursor.fetchall() - column_names = [col[1] for col in columns] - - if 'source' not in column_names: - print("Adding 'source' column to foods table...") - cursor.execute("ALTER TABLE foods ADD COLUMN source TEXT DEFAULT 'manual'") - print("✓ Added source column to foods table") - - # Check if meal_time column exists in meals table - cursor.execute("PRAGMA table_info(meals)") - columns = cursor.fetchall() - column_names = [col[1] for col in columns] - - if 'meal_time' not in column_names: - print("Adding 'meal_time' column to meals table...") - cursor.execute("ALTER TABLE meals ADD COLUMN meal_time TEXT DEFAULT 'Breakfast'") - print("✓ Added meal_time column to meals table") - - # Check if meal_time column exists in plans table - cursor.execute("PRAGMA table_info(plans)") - columns = cursor.fetchall() - column_names = [col[1] for col in columns] - - if 'meal_time' not in column_names: - print("Adding 'meal_time' column to plans table...") - cursor.execute("ALTER TABLE plans ADD COLUMN meal_time TEXT DEFAULT 'Breakfast'") - print("✓ Added meal_time column to plans table") - - # Update existing records to have proper source values - print("Updating existing food records with source information...") - - # Set source to 'csv' for foods that might have been imported - # Note: This is a heuristic - you may need to adjust based on your data - cursor.execute(""" - UPDATE foods - SET source = 'csv' - WHERE name LIKE '%(%' - AND source = 'manual' - """) - - conn.commit() - print("✓ Migration completed successfully!") - - except Exception as e: - print(f"✗ Migration failed: {e}") - conn.rollback() - finally: - conn.close() - -if __name__ == "__main__": - print("Starting database migration...") - migrate_database() - print("Migration script completed.") \ No newline at end of file diff --git a/migrate_food_brand.py b/migrate_food_brand.py deleted file mode 100644 index 4024441..0000000 --- a/migrate_food_brand.py +++ /dev/null @@ -1,61 +0,0 @@ -from sqlalchemy import create_engine, Column, Integer, String, Float -from sqlalchemy.ext.declarative import declarative_base -from sqlalchemy.orm import sessionmaker, Session -import re -import os - -DATABASE_URL = f"sqlite:///{os.getenv('DATABASE_PATH', './data')}/meal_planner.db" -engine = create_engine(DATABASE_URL, connect_args={"check_same_thread": False}) -SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) -Base = declarative_base() - -class Food(Base): - __tablename__ = "foods" - id = Column(Integer, primary_key=True, index=True) - name = Column(String, unique=True, index=True) - brand = Column(String, default="") # New field - -def get_db(): - db = SessionLocal() - try: - yield db - finally: - db.close() - -def migrate_food_brands(): - db = next(get_db()) - foods = db.query(Food).all() - - updated_count = 0 - for food in foods: - # Check if the name contains a brand in parentheses - match = re.search(r'\s*\((\w[^)]*)\)$', food.name) - if match: - brand_name = match.group(1).strip() - # If brand is found and not already set, update - if not food.brand and brand_name: - food.brand = brand_name - # Optionally remove brand from name - food.name = re.sub(r'\s*\((\w[^)]*)\)$', '', food.name).strip() - updated_count += 1 - print(f"Updated food '{food.name}' with brand '{food.brand}'") - - db.commit() - print(f"Migration complete. Updated {updated_count} food brands.") - db.close() - -if __name__ == "__main__": - print("Starting food brand migration...") - # This will add the 'brand' column if it doesn't exist. - # Note: For SQLite, ALTER TABLE ADD COLUMN is limited. - # If the column already exists and you're just populating, Base.metadata.create_all() is fine. - # If you're adding a new column to an existing table, you might need Alembic for proper migrations. - # For this task, we'll assume the column is added manually or via a previous step. - # Base.metadata.create_all(bind=engine) # This line should only be run if the table/column is new and not yet in DB - - # We need to reflect the existing table schema to ensure the 'brand' column is known - # by SQLAlchemy before attempting to set its value. - # For a real-world scenario, a proper migration tool like Alembic would handle schema changes. - # For this simplified example, we assume the 'brand' column already exists in the DB or will be added manually. - - migrate_food_brands() \ No newline at end of file diff --git a/migrate_to_dates.py b/migrate_to_dates.py deleted file mode 100644 index b5a1ef3..0000000 --- a/migrate_to_dates.py +++ /dev/null @@ -1,128 +0,0 @@ -#!/usr/bin/env python3 -""" -Migration script to convert Plan.date from String to Date type -and convert existing "DayX" values to actual calendar dates. -""" - -import sqlite3 -from datetime import datetime, timedelta - -def migrate_plans_to_dates(): - """Convert existing DayX plans to actual dates""" - - # Connect to database - conn = sqlite3.connect('meal_planner.db') - cursor = conn.cursor() - - try: - # Check if migration is needed - cursor.execute("PRAGMA table_info(plans)") - columns = cursor.fetchall() - date_column_type = None - - for col in columns: - if col[1] == 'date': # column name - date_column_type = col[2] # column type - break - - if date_column_type and 'DATE' in date_column_type.upper(): - print("Migration already completed - date column is already DATE type") - return - - print("Starting migration from String to Date...") - - # Create backup - print("Creating backup of plans table...") - cursor.execute(""" - CREATE TABLE plans_backup AS - SELECT * FROM plans - """) - - # Add new date column - print("Adding new date column...") - cursor.execute(""" - ALTER TABLE plans ADD COLUMN date_new DATE - """) - - # Convert DayX to actual dates (starting from today as Day1) - print("Converting DayX values to dates...") - today = datetime.now().date() - - # Get all unique day values - cursor.execute("SELECT DISTINCT date FROM plans WHERE date LIKE 'Day%'") - day_values = cursor.fetchall() - - for (day_str,) in day_values: - if day_str.startswith('Day'): - try: - day_num = int(day_str[3:]) # Extract number from "Day1", "Day2", etc. - # Convert to date (Day1 = today, Day2 = tomorrow, etc.) - actual_date = today + timedelta(days=day_num - 1) - - cursor.execute(""" - UPDATE plans - SET date_new = ? - WHERE date = ? - """, (actual_date.isoformat(), day_str)) - - print(f"Converted {day_str} to {actual_date.isoformat()}") - - except (ValueError, IndexError) as e: - print(f"Error converting {day_str}: {e}") - - # Handle any non-DayX dates (if they exist) - cursor.execute(""" - UPDATE plans - SET date_new = date - WHERE date NOT LIKE 'Day%' AND date_new IS NULL - """) - - # Recreate table with new structure (SQLite doesn't support DROP COLUMN with indexes) - print("Recreating table with new structure...") - cursor.execute(""" - CREATE TABLE plans_new ( - id INTEGER PRIMARY KEY, - person VARCHAR NOT NULL, - date DATE NOT NULL, - meal_id INTEGER NOT NULL REFERENCES meals(id) - ) - """) - - # Copy data to new table - cursor.execute(""" - INSERT INTO plans_new (id, person, date, meal_id) - SELECT id, person, date_new, meal_id FROM plans - """) - - # Drop old table and rename new one - cursor.execute("DROP TABLE plans") - cursor.execute("ALTER TABLE plans_new RENAME TO plans") - - # Create index on new date column - cursor.execute("CREATE INDEX ix_plans_date ON plans(date)") - - conn.commit() - print("Migration completed successfully!") - - # Show summary - cursor.execute("SELECT COUNT(*) FROM plans") - total_plans = cursor.fetchone()[0] - print(f"Total plans migrated: {total_plans}") - - except Exception as e: - print(f"Migration failed: {e}") - conn.rollback() - - # Restore backup if something went wrong - try: - cursor.execute("DROP TABLE IF EXISTS plans") - cursor.execute("ALTER TABLE plans_backup RENAME TO plans") - print("Restored from backup") - except Exception as backup_error: - print(f"Failed to restore backup: {backup_error}") - - finally: - conn.close() - -if __name__ == "__main__": - migrate_plans_to_dates() \ No newline at end of file diff --git a/migrate_tracker_modified.py b/migrate_tracker_modified.py deleted file mode 100644 index fb1a822..0000000 --- a/migrate_tracker_modified.py +++ /dev/null @@ -1,52 +0,0 @@ -#!/usr/bin/env python3 -""" -Migration script to add is_modified column to tracked_days table. -Run this script to add the modification tracking functionality. -""" - -import sqlite3 -import os - -def migrate_add_is_modified(): - """Add is_modified column to tracked_days table""" - db_path = "./meal_planner.db" - - if not os.path.exists(db_path): - print("Database file not found. Please run the main application first to create the database.") - return - - try: - conn = sqlite3.connect(db_path) - cursor = conn.cursor() - - # Check if column already exists - cursor.execute("PRAGMA table_info(tracked_days)") - columns = cursor.fetchall() - column_names = [col[1] for col in columns] - - if 'is_modified' in column_names: - print("Column 'is_modified' already exists in tracked_days table.") - conn.close() - return - - print("Adding is_modified column to tracked_days table...") - - # Add the column with default value 0 (False) - cursor.execute("ALTER TABLE tracked_days ADD COLUMN is_modified INTEGER DEFAULT 0") - - # Update existing rows to have is_modified = 0 (not modified) - cursor.execute("UPDATE tracked_days SET is_modified = 0 WHERE is_modified IS NULL") - - conn.commit() - conn.close() - - print("Migration completed successfully!") - print("Added column: is_modified (INTEGER, DEFAULT 0)") - - except Exception as e: - print(f"Migration failed: {e}") - if 'conn' in locals(): - conn.close() - -if __name__ == "__main__": - migrate_add_is_modified() \ No newline at end of file diff --git a/migrate_tracker_schema.py b/migrate_tracker_schema.py deleted file mode 100644 index ad6ac25..0000000 --- a/migrate_tracker_schema.py +++ /dev/null @@ -1,50 +0,0 @@ -#!/usr/bin/env python3 -""" -Migration script to add tracker tables for meal tracking functionality. -Run this script to add the necessary tables for the Tracker tab. -""" - -from sqlalchemy import create_engine, Column, Integer, String, Float, DateTime, ForeignKey, Date -from sqlalchemy.ext.declarative import declarative_base -from sqlalchemy.orm import sessionmaker -import os - -# Database setup -DATABASE_URL = f"sqlite:///{os.getenv('DATABASE_PATH', './data')}/meal_planner.db" -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) -Base = declarative_base() - -# New models for tracker functionality -class TrackedDay(Base): - """Represents a day being tracked (separate from planned days)""" - __tablename__ = "tracked_days" - - id = Column(Integer, primary_key=True, index=True) - person = Column(String, index=True) # Sarah or Stuart - date = Column(Date, index=True) # Date being tracked - -class TrackedMeal(Base): - """Represents a meal tracked for a specific day""" - __tablename__ = "tracked_meals" - - id = Column(Integer, primary_key=True, index=True) - tracked_day_id = Column(Integer) # Will add FK constraint later - meal_id = Column(Integer) # Will add FK constraint later - meal_time = Column(String) # Breakfast, Lunch, Dinner, Snack 1, Snack 2, Beverage 1, Beverage 2 - quantity = Column(Float, default=1.0) # Quantity multiplier (e.g., 1.5 for 1.5 servings) - -def migrate_tracker_tables(): - """Create the new tracker tables""" - try: - print("Creating tracker tables...") - Base.metadata.create_all(bind=engine) - print("Migration completed successfully!") - print("New tables created:") - print("- tracked_days: Stores individual days being tracked") - print("- tracked_meals: Stores meals for each tracked day") - except Exception as e: - print(f"Migration failed: {e}") - -if __name__ == "__main__": - migrate_tracker_tables() \ No newline at end of file diff --git a/plan.md b/plan.md deleted file mode 100644 index 76295a1..0000000 --- a/plan.md +++ /dev/null @@ -1,92 +0,0 @@ -# Plan for Adding New Tests to test_detailed.py - -## Overview -This plan outlines the additional tests that need to be added to `tests/test_detailed.py` to cover the following functionality: -- Load today's date by default -- View date should return the meals planned for a date (already covered) -- The template dropdown should show a list of templates available to view - -## Current Test Coverage -The existing tests in `tests/test_detailed.py` already cover: -- `test_detailed_page_no_params` - when no params are provided -- `test_detailed_page_with_plan_date` - when plan_date is provided -- `test_detailed_page_with_template_id` - when template_id is provided -- `test_detailed_page_with_invalid_plan_date` - when invalid plan_date is provided -- `test_detailed_page_with_invalid_template_id` - when invalid template_id is provided - -## New Tests to Add - -### 1. Test Default Date Loading -**Test Name:** `test_detailed_page_default_date` -**Purpose:** Verify that when no plan_date is provided, the detailed page loads with today's date by default -**Implementation:** -```python -def test_detailed_page_default_date(client, session): - # Create mock data for today - 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() - - # Test that when no plan_date is provided, today's date is used by default - response = client.get("/detailed?person=Sarah") - assert response.status_code == 200 - assert "Sarah's Detailed Plan for" in response.text - assert test_date.strftime('%B %d, %Y') in response.text # Check if today's date appears in the formatted date - assert "Fruit Snack" in response.text -``` - -### 2. Test Template Dropdown -**Test Name:** `test_detailed_page_template_dropdown` -**Purpose:** Verify that the template dropdown shows available templates -**Implementation:** -```python -def test_detailed_page_template_dropdown(client, session): - # Create multiple templates - template1 = Template(name="Morning Boost") - template2 = Template(name="Evening Energy") - session.add(template1) - session.add(template2) - session.commit() - session.refresh(template1) - session.refresh(template2) - - # Test that the template dropdown shows available templates - response = client.get("/detailed") - assert response.status_code == 200 - - # Check that the response contains template selection UI elements - assert "Select Template..." in response.text - assert "Morning Boost" in response.text - assert "Evening Energy" in response.text - - # Verify that template IDs are present in the dropdown options - assert f'value="{template1.id}"' in response.text - assert f'value="{template2.id}"' in response.text -``` - -## Implementation Notes -- Both tests should use the existing session and client fixtures -- The tests should create necessary mock data to ensure proper functionality testing -- The date default test should verify that today's date appears in the response when no date is specified -- The template dropdown test should verify that templates are properly listed in the UI - -## Expected Outcome -After implementing these tests, the test coverage for the detailed page will include: -- Default date loading functionality -- Template dropdown functionality -- All existing functionality remains covered \ No newline at end of file