#!/usr/bin/env python3 """ Test script for Story 3.3: Summary History Management Tests backend and frontend implementation """ import sys import os import json import requests from datetime import datetime, timedelta from typing import Dict, List, Optional # Add backend directory to path backend_dir = os.path.join(os.path.dirname(__file__), 'backend') sys.path.insert(0, backend_dir) class Story33Tester: def __init__(self): self.base_url = "http://localhost:8000" self.test_user = { "email": "test_history@example.com", "password": "TestPass123!", "full_name": "Test User" } self.access_token = None self.user_id = None def print_section(self, title: str): """Print a section header""" print("\n" + "=" * 60) print(f" {title}") print("=" * 60) def print_result(self, test_name: str, passed: bool, details: str = ""): """Print test result""" status = "āœ… PASS" if passed else "āŒ FAIL" print(f"{status} - {test_name}") if details: print(f" {details}") def test_backend_models(self) -> bool: """Test if backend models have history fields""" self.print_section("Testing Backend Models") try: from backend.models.summary import Summary from backend.core.database_registry import registry # Check for new fields required_fields = ['is_starred', 'notes', 'tags', 'shared_token', 'is_public', 'view_count'] model_fields = [col.name for col in Summary.__table__.columns] all_fields_present = True for field in required_fields: present = field in model_fields self.print_result(f"Field '{field}' in Summary model", present) if not present: all_fields_present = False # Check for generate_share_token method has_share_method = hasattr(Summary, 'generate_share_token') self.print_result("generate_share_token method exists", has_share_method) return all_fields_present and has_share_method except ImportError as e: self.print_result("Import backend models", False, str(e)) return False def test_database_migration(self) -> bool: """Test if database migration exists""" self.print_section("Testing Database Migration") migration_path = os.path.join( backend_dir, 'alembic/versions/add_history_management_fields.py' ) exists = os.path.exists(migration_path) self.print_result("Migration file exists", exists, migration_path) if exists: with open(migration_path, 'r') as f: content = f.read() # Check for key migration operations checks = [ ('is_starred column', "add_column(sa.Column('is_starred'"), ('notes column', "add_column(sa.Column('notes'"), ('tags column', "add_column(sa.Column('tags'"), ('shared_token column', "add_column(sa.Column('shared_token'"), ('indexes created', "create_index"), ] for check_name, check_str in checks: found = check_str in content self.print_result(f"Migration includes {check_name}", found) return exists def test_api_endpoints(self) -> bool: """Test API endpoints availability""" self.print_section("Testing API Endpoints") # First, register and login try: # Register user register_resp = requests.post( f"{self.base_url}/api/auth/register", json=self.test_user ) if register_resp.status_code == 400: # User might already exist, try login login_resp = requests.post( f"{self.base_url}/api/auth/login", json={ "email": self.test_user["email"], "password": self.test_user["password"] } ) if login_resp.status_code == 200: self.access_token = login_resp.json()["access_token"] self.user_id = login_resp.json()["user"]["id"] elif register_resp.status_code == 201: self.access_token = register_resp.json()["access_token"] self.user_id = register_resp.json()["user"]["id"] except Exception as e: self.print_result("Authentication setup", False, str(e)) return False if not self.access_token: self.print_result("Authentication", False, "Could not obtain access token") return False headers = {"Authorization": f"Bearer {self.access_token}"} # Test endpoints endpoints = [ ("GET /api/summaries", "get", "/api/summaries", None), ("GET /api/summaries/starred", "get", "/api/summaries/starred", None), ("GET /api/summaries/stats", "get", "/api/summaries/stats", None), ("POST /api/summaries/search", "post", "/api/summaries/search", {"query": "test"}), ] all_passed = True for name, method, path, data in endpoints: try: if method == "get": resp = requests.get(f"{self.base_url}{path}", headers=headers) else: resp = requests.post(f"{self.base_url}{path}", json=data, headers=headers) passed = resp.status_code in [200, 201, 404] # 404 ok for empty results self.print_result(name, passed, f"Status: {resp.status_code}") if not passed: all_passed = False except Exception as e: self.print_result(name, False, str(e)) all_passed = False return all_passed def test_frontend_components(self) -> bool: """Test if frontend components exist""" self.print_section("Testing Frontend Components") frontend_dir = os.path.join(os.path.dirname(__file__), 'frontend/src') components = [ ('SummaryHistoryPage', 'pages/history/SummaryHistoryPage.tsx'), ('useSummaryHistory hook', 'hooks/useSummaryHistory.ts'), ('summaryAPI service', 'services/summaryAPI.ts'), ('SummaryList component', 'components/summaries/SummaryList.tsx'), ('SummaryCard component', 'components/summaries/SummaryCard.tsx'), ('SummarySearch component', 'components/summaries/SummarySearch.tsx'), ('BulkActions component', 'components/summaries/BulkActions.tsx'), ('ExportDialog component', 'components/summaries/ExportDialog.tsx'), ('UsageStats component', 'components/summaries/UsageStats.tsx'), ] all_exist = True for name, path in components: full_path = os.path.join(frontend_dir, path) exists = os.path.exists(full_path) self.print_result(f"{name} exists", exists, path) if not exists: all_exist = False return all_exist def test_routing(self) -> bool: """Test if routing is configured""" self.print_section("Testing Routing Configuration") app_file = os.path.join( os.path.dirname(__file__), 'frontend/src/App.tsx' ) if os.path.exists(app_file): with open(app_file, 'r') as f: content = f.read() checks = [ ('SummaryHistoryPage import', 'import { SummaryHistoryPage }'), ('History route configured', 'path="/history"'), ('Protected route for history', ' bool: """Run all tests""" print("\n" + "šŸŽÆ" * 30) print(" STORY 3.3: SUMMARY HISTORY MANAGEMENT - TEST SUITE") print("šŸŽÆ" * 30) results = { "Backend Models": self.test_backend_models(), "Database Migration": self.test_database_migration(), "API Endpoints": self.test_api_endpoints(), "Frontend Components": self.test_frontend_components(), "Routing Configuration": self.test_routing(), } # Summary self.print_section("TEST SUMMARY") total = len(results) passed = sum(1 for v in results.values() if v) for test_name, result in results.items(): status = "āœ…" if result else "āŒ" print(f"{status} {test_name}") print(f"\nTotal: {passed}/{total} test groups passed") if passed == total: print("\nšŸŽ‰ SUCCESS! Story 3.3 is fully implemented!") print("\nNext steps:") print("1. Run the application: cd frontend && npm run dev") print("2. Navigate to http://localhost:3000/history") print("3. Test the summary history functionality") print("4. Update Epic 3 documentation to reflect completion") else: print("\nāš ļø Some tests failed. Please review and fix the issues above.") return passed == total if __name__ == "__main__": # Check if backend server is running try: response = requests.get("http://localhost:8000/health") if response.status_code != 200: print("āš ļø Backend server is not healthy. Please start it first:") print(" cd backend && python main.py") sys.exit(1) except requests.exceptions.ConnectionError: print("āŒ Backend server is not running. Please start it first:") print(" cd backend && python main.py") sys.exit(1) tester = Story33Tester() success = tester.run_all_tests() sys.exit(0 if success else 1)