diff --git a/README.md b/README.md index b26a61f..2659357 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,7 @@ A simple Todo application built with FastAPI and SQLite. - Database migrations with Alembic - Health check endpoint - OpenAPI documentation +- Test script for API functionality ## Project Structure @@ -32,6 +33,7 @@ todoapplication/ ├── alembic.ini # Alembic configuration ├── main.py # Application entry point ├── requirements.txt # Python dependencies +├── test_api.py # API testing script ``` ## API Endpoints @@ -81,3 +83,13 @@ Create a new migration: ```bash alembic revision -m "your migration message" ``` + +### Testing + +Run the API tests (make sure the server is running first): + +```bash +python test_api.py +``` + +This will test all the CRUD operations for the Todo API. diff --git a/app/core/config.py b/app/core/config.py index 647dcfa..f17a6dc 100644 --- a/app/core/config.py +++ b/app/core/config.py @@ -1,15 +1,17 @@ from pydantic import BaseModel from typing import Optional from pathlib import Path +import os class Settings(BaseModel): API_V1_STR: str = "/api/v1" PROJECT_NAME: str = "Todo API" PROJECT_DESCRIPTION: str = "A simple Todo API built with FastAPI and SQLite" VERSION: str = "0.1.0" - + # Database settings - DB_DIR: Path = Path("/app") / "storage" / "db" + BASE_DIR: Path = Path(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))) + DB_DIR: Path = BASE_DIR / "storage" / "db" SQLALCHEMY_DATABASE_URL: Optional[str] = None class Config: diff --git a/requirements.txt b/requirements.txt index c124ef7..f08cb7c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,4 +4,5 @@ sqlalchemy==2.0.27 pydantic==2.5.3 alembic==1.12.1 python-dotenv==1.0.0 -pathlib==1.0.1 \ No newline at end of file +pathlib==1.0.1 +requests==2.31.0 \ No newline at end of file diff --git a/test_api.py b/test_api.py new file mode 100644 index 0000000..78cf02c --- /dev/null +++ b/test_api.py @@ -0,0 +1,151 @@ +#!/usr/bin/env python3 +""" +Test script for the Todo API +This script tests all the CRUD operations for the Todo API +""" + +import requests +import json +import sys +from typing import Dict, Any, Optional + +BASE_URL = "http://localhost:8000/api/v1" + +def print_success(message: str) -> None: + """Print a success message in green color""" + print(f"\033[92m✓ {message}\033[0m") + +def print_error(message: str) -> None: + """Print an error message in red color""" + print(f"\033[91m✗ {message}\033[0m") + +def print_info(message: str) -> None: + """Print an info message in blue color""" + print(f"\033[94m• {message}\033[0m") + +def test_health_endpoint() -> bool: + """Test the health endpoint""" + print_info("Testing health endpoint...") + try: + response = requests.get("http://localhost:8000/health") + if response.status_code == 200: + data = response.json() + if data.get("status") == "healthy": + print_success("Health endpoint is working") + return True + else: + print_error(f"Health endpoint returned unexpected data: {data}") + return False + else: + print_error(f"Health endpoint returned status code {response.status_code}") + return False + except Exception as e: + print_error(f"Error testing health endpoint: {e}") + return False + +def create_todo(title: str, description: Optional[str] = None, completed: bool = False) -> Dict[str, Any]: + """Create a new todo and return the created todo data""" + print_info(f"Creating todo: {title}") + todo_data = { + "title": title, + "description": description, + "completed": completed + } + + response = requests.post(f"{BASE_URL}/todos/", json=todo_data) + if response.status_code == 201: + todo = response.json() + print_success(f"Todo created with ID: {todo['id']}") + return todo + else: + print_error(f"Failed to create todo. Status code: {response.status_code}") + print_error(f"Response: {response.text}") + return {} + +def get_todo(todo_id: int) -> Dict[str, Any]: + """Get a specific todo by ID""" + print_info(f"Getting todo with ID: {todo_id}") + response = requests.get(f"{BASE_URL}/todos/{todo_id}") + if response.status_code == 200: + todo = response.json() + print_success(f"Got todo: {todo['title']}") + return todo + else: + print_error(f"Failed to get todo. Status code: {response.status_code}") + print_error(f"Response: {response.text}") + return {} + +def get_all_todos() -> list: + """Get all todos""" + print_info("Getting all todos") + response = requests.get(f"{BASE_URL}/todos/") + if response.status_code == 200: + todos = response.json() + print_success(f"Got {len(todos)} todos") + return todos + else: + print_error(f"Failed to get todos. Status code: {response.status_code}") + print_error(f"Response: {response.text}") + return [] + +def update_todo(todo_id: int, data: Dict[str, Any]) -> Dict[str, Any]: + """Update a specific todo""" + print_info(f"Updating todo with ID: {todo_id}") + response = requests.put(f"{BASE_URL}/todos/{todo_id}", json=data) + if response.status_code == 200: + todo = response.json() + print_success(f"Updated todo: {todo['title']}") + return todo + else: + print_error(f"Failed to update todo. Status code: {response.status_code}") + print_error(f"Response: {response.text}") + return {} + +def delete_todo(todo_id: int) -> bool: + """Delete a specific todo""" + print_info(f"Deleting todo with ID: {todo_id}") + response = requests.delete(f"{BASE_URL}/todos/{todo_id}") + if response.status_code == 204: + print_success(f"Deleted todo with ID: {todo_id}") + return True + else: + print_error(f"Failed to delete todo. Status code: {response.status_code}") + print_error(f"Response: {response.text}") + return False + +def run_tests() -> None: + """Run all tests""" + print_info("Starting API tests") + + # Test health endpoint + if not test_health_endpoint(): + print_error("Health endpoint test failed. Make sure the server is running.") + sys.exit(1) + + # Test creating a todo + todo = create_todo("Test Todo", "This is a test todo", False) + if not todo: + print_error("Failed to create todo. Stopping tests.") + sys.exit(1) + + todo_id = todo["id"] + + # Test getting a specific todo + get_todo(todo_id) + + # Test getting all todos + get_all_todos() + + # Test updating a todo + updated_todo = update_todo(todo_id, {"title": "Updated Test Todo", "completed": True}) + if not updated_todo: + print_error("Failed to update todo.") + + # Test deleting a todo + if not delete_todo(todo_id): + print_error("Failed to delete todo.") + + print_info("All tests completed") + +if __name__ == "__main__": + run_tests() \ No newline at end of file