Initial commit for resumeformatter project
This commit is contained in:
85
backend/Dockerfile
Normal file
85
backend/Dockerfile
Normal file
@@ -0,0 +1,85 @@
|
||||
FROM node:20-alpine as builder
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Add build arg to bust cache
|
||||
ARG CACHEBUST=1
|
||||
|
||||
# Copy package files from frontend directory
|
||||
COPY frontend/package.json ./
|
||||
|
||||
# Install dependencies
|
||||
RUN npm install
|
||||
|
||||
# Copy the frontend application
|
||||
COPY frontend/ ./
|
||||
|
||||
# Build the application with cache busting
|
||||
RUN echo "Building with cache bust: $CACHEBUST" && npm run build
|
||||
|
||||
# Verify build output
|
||||
RUN ls -la /app/dist || echo "Dist directory not created"
|
||||
RUN ls -la /app/dist/assets || echo "Assets directory not created"
|
||||
|
||||
# Production stage with FastAPI
|
||||
FROM python:3.11-alpine as production
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Install system dependencies
|
||||
RUN apk add --no-cache curl
|
||||
|
||||
# Copy Python requirements and install dependencies
|
||||
COPY backend/requirements.txt ./
|
||||
RUN pip install --no-cache-dir -r requirements.txt
|
||||
|
||||
# Copy built frontend assets from the builder stage
|
||||
COPY --from=builder /app/dist /app/dist
|
||||
|
||||
# Copy FastAPI application
|
||||
COPY backend/app /app/app
|
||||
COPY backend/main.py ./
|
||||
|
||||
# Expose the port the app runs on
|
||||
EXPOSE 8080
|
||||
|
||||
# Add health check
|
||||
HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
|
||||
CMD curl -f http://localhost:8080/api/health || exit 1
|
||||
|
||||
# Command to run the application
|
||||
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8080"]
|
||||
|
||||
# Development stage with FastAPI
|
||||
FROM python:3.11-alpine as development
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Install system dependencies
|
||||
RUN apk add --no-cache curl nodejs npm
|
||||
|
||||
# Copy Python requirements and install dependencies
|
||||
COPY backend/requirements.txt ./
|
||||
RUN pip install --no-cache-dir -r requirements.txt
|
||||
|
||||
# Copy built frontend assets from the builder stage
|
||||
COPY --from=builder /app/dist /app/dist
|
||||
|
||||
# Verify the copied frontend build
|
||||
RUN ls -la /app/dist || echo "Dist directory not created"
|
||||
RUN ls -la /app/dist/assets || echo "Assets directory not created"
|
||||
|
||||
# Copy FastAPI application
|
||||
COPY backend/app /app/app
|
||||
COPY backend/main.py ./
|
||||
|
||||
# Expose the ports the app runs on (FastAPI and Vite dev server)
|
||||
EXPOSE 8080 5173
|
||||
|
||||
# Add health check
|
||||
HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
|
||||
CMD curl -f http://localhost:8080/api/health || exit 1
|
||||
|
||||
# Command to run the application (FastAPI only in development)
|
||||
# Frontend will be built and served by FastAPI
|
||||
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8080", "--reload"]
|
||||
80
backend/README.md
Normal file
80
backend/README.md
Normal file
@@ -0,0 +1,80 @@
|
||||
# Profile Linker API
|
||||
|
||||
This is a FastAPI backend for the Profile Linker application. It provides API endpoints for managing a list of people's profiles.
|
||||
|
||||
## Project Structure
|
||||
|
||||
The project follows a modular structure:
|
||||
|
||||
```
|
||||
backend/
|
||||
├── app/
|
||||
│ ├── api/ # API endpoints
|
||||
│ │ ├── endpoints/ # API endpoint modules
|
||||
│ │ └── api.py # API router
|
||||
│ ├── core/ # Core application components
|
||||
│ │ └── config.py # Application settings
|
||||
│ ├── crud/ # CRUD operations
|
||||
│ │ └── person.py # Person CRUD operations
|
||||
│ ├── db/ # Database components
|
||||
│ │ ├── base.py # Database base
|
||||
│ │ ├── base_class.py # Base class for database models
|
||||
│ │ └── session.py # Database session
|
||||
│ ├── models/ # Database models
|
||||
│ │ └── person.py # Person model
|
||||
│ ├── schemas/ # Pydantic schemas
|
||||
│ │ └── person.py # Person schemas
|
||||
│ └── main.py # FastAPI application
|
||||
├── main.py # Entry point
|
||||
└── requirements.txt # Python dependencies
|
||||
```
|
||||
|
||||
## API Endpoints
|
||||
|
||||
The API provides the following endpoints:
|
||||
|
||||
- `GET /api/people`: Get all people
|
||||
- `POST /api/people`: Create a new person
|
||||
|
||||
## Development
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- Python 3.11+
|
||||
- Docker and Docker Compose
|
||||
|
||||
### Running the Application
|
||||
|
||||
#### Using Docker Compose
|
||||
|
||||
1. Build and start the containers:
|
||||
|
||||
```bash
|
||||
docker-compose up --build
|
||||
```
|
||||
|
||||
2. Access the API at http://localhost:8080/api
|
||||
3. Access the API documentation at http://localhost:8080/docs
|
||||
|
||||
#### Running Locally
|
||||
|
||||
1. Install dependencies:
|
||||
|
||||
```bash
|
||||
cd backend
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
2. Run the application:
|
||||
|
||||
```bash
|
||||
cd backend
|
||||
python main.py
|
||||
```
|
||||
|
||||
3. Access the API at http://localhost:8080/api
|
||||
4. Access the API documentation at http://localhost:8080/docs
|
||||
|
||||
## Frontend Integration
|
||||
|
||||
The backend serves the frontend as static assets. The frontend should be built and placed in the `dist` directory. The Docker setup handles this automatically.
|
||||
0
backend/app/__init__.py
Normal file
0
backend/app/__init__.py
Normal file
0
backend/app/api/__init__.py
Normal file
0
backend/app/api/__init__.py
Normal file
6
backend/app/api/api.py
Normal file
6
backend/app/api/api.py
Normal file
@@ -0,0 +1,6 @@
|
||||
from fastapi import APIRouter
|
||||
|
||||
from app.api.endpoints import people
|
||||
|
||||
api_router = APIRouter()
|
||||
api_router.include_router(people.router, prefix="/people", tags=["people"])
|
||||
0
backend/app/api/endpoints/__init__.py
Normal file
0
backend/app/api/endpoints/__init__.py
Normal file
35
backend/app/api/endpoints/people.py
Normal file
35
backend/app/api/endpoints/people.py
Normal file
@@ -0,0 +1,35 @@
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
from sqlalchemy.orm import Session
|
||||
from typing import List
|
||||
|
||||
from app.db.session import get_db
|
||||
from app.schemas.person import Person, PersonCreate
|
||||
from app.crud import person
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
@router.get("/", response_model=List[Person])
|
||||
def get_all_people(db: Session = Depends(get_db)):
|
||||
"""
|
||||
Get all people
|
||||
"""
|
||||
return person.get_all(db)
|
||||
|
||||
|
||||
@router.post("/", response_model=Person, status_code=201)
|
||||
def create_person(
|
||||
*,
|
||||
db: Session = Depends(get_db),
|
||||
person_in: PersonCreate
|
||||
):
|
||||
"""
|
||||
Create a new person
|
||||
"""
|
||||
try:
|
||||
return person.create(db=db, obj_in=person_in)
|
||||
except Exception as e:
|
||||
raise HTTPException(
|
||||
status_code=500,
|
||||
detail="Failed to create the person."
|
||||
)
|
||||
25
backend/app/core/config.py
Normal file
25
backend/app/core/config.py
Normal file
@@ -0,0 +1,25 @@
|
||||
from typing import Optional, List
|
||||
import os
|
||||
from dotenv import load_dotenv
|
||||
|
||||
# Load environment variables
|
||||
load_dotenv()
|
||||
|
||||
# Simple settings class without using BaseSettings
|
||||
class Settings:
|
||||
"""
|
||||
Application settings
|
||||
"""
|
||||
APP_NAME: str = os.getenv("APP_NAME", "ResumeFormatter")
|
||||
API_V1_STR: str = f"/{APP_NAME}/api"
|
||||
PROJECT_NAME: str = "Profile Linker API"
|
||||
|
||||
# CORS settings
|
||||
BACKEND_CORS_ORIGINS: List[str] = ["*"]
|
||||
|
||||
# Database settings - using in-memory database by default
|
||||
# In a production environment, you would use a real database connection string
|
||||
DATABASE_URL: Optional[str] = None
|
||||
|
||||
|
||||
settings = Settings()
|
||||
1
backend/app/crud/__init__.py
Normal file
1
backend/app/crud/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
from . import person
|
||||
27
backend/app/crud/person.py
Normal file
27
backend/app/crud/person.py
Normal file
@@ -0,0 +1,27 @@
|
||||
from sqlalchemy.orm import Session
|
||||
from app.models.person import Person
|
||||
from app.schemas.person import PersonCreate
|
||||
import uuid
|
||||
|
||||
|
||||
def get_all(db: Session) -> list[Person]:
|
||||
"""
|
||||
Get all people from the database
|
||||
"""
|
||||
return db.query(Person).all()
|
||||
|
||||
|
||||
def create(db: Session, *, obj_in: PersonCreate) -> Person:
|
||||
"""
|
||||
Create a new person in the database
|
||||
"""
|
||||
db_obj = Person(
|
||||
id=str(uuid.uuid4()),
|
||||
firstName=obj_in.firstName,
|
||||
lastName=obj_in.lastName,
|
||||
linkedinUrl=obj_in.linkedinUrl
|
||||
)
|
||||
db.add(db_obj)
|
||||
db.commit()
|
||||
db.refresh(db_obj)
|
||||
return db_obj
|
||||
3
backend/app/db/base.py
Normal file
3
backend/app/db/base.py
Normal file
@@ -0,0 +1,3 @@
|
||||
# Import all the models, so that Base has them before being imported by Alembic
|
||||
from app.db.base_class import Base # noqa
|
||||
from app.models.person import Person # noqa
|
||||
13
backend/app/db/base_class.py
Normal file
13
backend/app/db/base_class.py
Normal file
@@ -0,0 +1,13 @@
|
||||
from typing import Any
|
||||
from sqlalchemy.ext.declarative import as_declarative, declared_attr
|
||||
|
||||
|
||||
@as_declarative()
|
||||
class Base:
|
||||
id: Any
|
||||
__name__: str
|
||||
|
||||
# Generate __tablename__ automatically based on class name
|
||||
@declared_attr
|
||||
def __tablename__(cls) -> str:
|
||||
return cls.__name__.lower()
|
||||
19
backend/app/db/session.py
Normal file
19
backend/app/db/session.py
Normal file
@@ -0,0 +1,19 @@
|
||||
from sqlalchemy import create_engine
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
from app.db.base_class import Base
|
||||
|
||||
# Use a file-based SQLite database instead of in-memory
|
||||
SQLALCHEMY_DATABASE_URL = "sqlite:///./ResumeFormatter.db"
|
||||
|
||||
engine = create_engine(
|
||||
SQLALCHEMY_DATABASE_URL, connect_args={"check_same_thread": False}
|
||||
)
|
||||
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
|
||||
|
||||
# Dependency to get DB session
|
||||
def get_db():
|
||||
db = SessionLocal()
|
||||
try:
|
||||
yield db
|
||||
finally:
|
||||
db.close()
|
||||
195
backend/app/main.py
Normal file
195
backend/app/main.py
Normal file
@@ -0,0 +1,195 @@
|
||||
from fastapi import FastAPI, Request
|
||||
from fastapi.staticfiles import StaticFiles
|
||||
from fastapi.responses import FileResponse, HTMLResponse, RedirectResponse
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
import os
|
||||
import pathlib
|
||||
from dotenv import load_dotenv
|
||||
|
||||
from app.api.api import api_router
|
||||
from app.core.config import settings
|
||||
from app.db.session import engine
|
||||
from app.db.base import Base
|
||||
|
||||
# Load environment variables
|
||||
load_dotenv()
|
||||
|
||||
# Create tables in the database
|
||||
Base.metadata.create_all(bind=engine)
|
||||
|
||||
app = FastAPI(
|
||||
title=settings.PROJECT_NAME,
|
||||
openapi_url=f"{settings.API_V1_STR}/openapi.json",
|
||||
docs_url=f"{settings.API_V1_STR}/docs",
|
||||
redoc_url=f"{settings.API_V1_STR}/redoc"
|
||||
)
|
||||
|
||||
# Set up CORS
|
||||
if settings.BACKEND_CORS_ORIGINS:
|
||||
app.add_middleware(
|
||||
CORSMiddleware,
|
||||
allow_origins=[str(origin) for origin in settings.BACKEND_CORS_ORIGINS],
|
||||
allow_credentials=True,
|
||||
allow_methods=["*"],
|
||||
allow_headers=["*"],
|
||||
)
|
||||
|
||||
# Include API router
|
||||
app.include_router(api_router, prefix=settings.API_V1_STR)
|
||||
|
||||
# Check if dist directory exists - use absolute path
|
||||
dist_path = pathlib.Path("/app/dist")
|
||||
if dist_path.exists():
|
||||
# Mount static files from the dist directory if it exists
|
||||
assets_path = dist_path / "assets"
|
||||
if assets_path.exists():
|
||||
# Mount assets with APP_NAME prefix
|
||||
app.mount(f"/{settings.APP_NAME}/assets", StaticFiles(directory=str(assets_path)), name="assets")
|
||||
print(f"Successfully mounted assets from {assets_path} at /{settings.APP_NAME}/assets")
|
||||
else:
|
||||
print(f"Warning: Dist directory {dist_path} does not exist")
|
||||
# Try local development path with frontend folder
|
||||
local_dist_path = pathlib.Path("frontend/dist")
|
||||
if local_dist_path.exists():
|
||||
local_assets_path = local_dist_path / "assets"
|
||||
if local_assets_path.exists():
|
||||
# Mount assets with APP_NAME prefix
|
||||
app.mount(f"/{settings.APP_NAME}/assets", StaticFiles(directory=str(local_assets_path)), name="assets")
|
||||
print(f"Successfully mounted assets from {local_assets_path} at /{settings.APP_NAME}/assets")
|
||||
|
||||
|
||||
@app.get("/api/health")
|
||||
async def health_check():
|
||||
return {"status": "ok"}
|
||||
|
||||
|
||||
# Redirect root to APP_NAME
|
||||
@app.get("/")
|
||||
async def redirect_to_app():
|
||||
return RedirectResponse(f"/{settings.APP_NAME}")
|
||||
|
||||
|
||||
# Handle index.css request
|
||||
@app.get("/index.css")
|
||||
async def serve_css():
|
||||
# Try to serve from /app/dist (container path)
|
||||
css_path = pathlib.Path("/app/dist/index.css")
|
||||
if css_path.exists():
|
||||
return FileResponse(css_path)
|
||||
|
||||
# Try to serve from frontend/dist (local development path)
|
||||
local_css_path = pathlib.Path("frontend/dist/index.css")
|
||||
if local_css_path.exists():
|
||||
return FileResponse(local_css_path)
|
||||
|
||||
return {"error": "CSS file not found"}
|
||||
|
||||
|
||||
# Handle vite.svg request
|
||||
@app.get("/vite.svg")
|
||||
async def serve_favicon():
|
||||
# Try to serve from /app/dist (container path)
|
||||
favicon_path = pathlib.Path("/app/dist/vite.svg")
|
||||
if favicon_path.exists():
|
||||
return FileResponse(favicon_path)
|
||||
|
||||
# Try to serve from frontend/dist (local development path)
|
||||
local_favicon_path = pathlib.Path("frontend/dist/vite.svg")
|
||||
if local_favicon_path.exists():
|
||||
return FileResponse(local_favicon_path)
|
||||
|
||||
return {"error": "Favicon not found"}
|
||||
|
||||
|
||||
# Serve index.html for the APP_NAME route
|
||||
@app.get("/{app_name}")
|
||||
async def serve_app(app_name: str):
|
||||
if app_name != settings.APP_NAME:
|
||||
return {"error": "App not found"}
|
||||
|
||||
# Try to serve from /app/dist (container path)
|
||||
index_path = pathlib.Path("/app/dist/index.html")
|
||||
if index_path.exists():
|
||||
return FileResponse(index_path)
|
||||
|
||||
# Try to serve from frontend/dist (local development path)
|
||||
local_index_path = pathlib.Path("frontend/dist/index.html")
|
||||
if local_index_path.exists():
|
||||
return FileResponse(local_index_path)
|
||||
|
||||
# If neither exists, return a simple HTML response
|
||||
html_content = """
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Profile Linker API</title>
|
||||
<style>
|
||||
body { font-family: Arial, sans-serif; margin: 40px; line-height: 1.6; }
|
||||
h1 { color: #333; }
|
||||
.container { max-width: 800px; margin: 0 auto; }
|
||||
.message { background-color: #f8f9fa; border-left: 4px solid #007bff; padding: 15px; }
|
||||
code { background-color: #f1f1f1; padding: 2px 5px; border-radius: 3px; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>FastAPI Server Running</h1>
|
||||
<div class="message">
|
||||
<p>The FastAPI server is running correctly, but the frontend build files are not available.</p>
|
||||
<p>To see the frontend, make sure to build it first with <code>npm run build</code>.</p>
|
||||
<p>API endpoints are available at <a href="/docs">/docs</a>.</p>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
"""
|
||||
return HTMLResponse(content=html_content, status_code=200)
|
||||
|
||||
|
||||
# Serve index.html for all other routes to support SPA routing
|
||||
@app.get("/{app_name}/{full_path:path}")
|
||||
async def serve_spa(app_name: str, full_path: str):
|
||||
if app_name != settings.APP_NAME:
|
||||
return {"error": "App not found"}
|
||||
|
||||
# Check if the path is an API route
|
||||
if full_path.startswith("api/"):
|
||||
return {"error": "API route not found"}
|
||||
|
||||
# Try to serve from /app/dist (container path)
|
||||
index_path = pathlib.Path("/app/dist/index.html")
|
||||
if index_path.exists():
|
||||
return FileResponse(index_path)
|
||||
|
||||
# Try to serve from frontend/dist (local development path)
|
||||
local_index_path = pathlib.Path("frontend/dist/index.html")
|
||||
if local_index_path.exists():
|
||||
return FileResponse(local_index_path)
|
||||
|
||||
# If neither exists, return a simple HTML response
|
||||
html_content = """
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Profile Linker API</title>
|
||||
<style>
|
||||
body { font-family: Arial, sans-serif; margin: 40px; line-height: 1.6; }
|
||||
h1 { color: #333; }
|
||||
.container { max-width: 800px; margin: 0 auto; }
|
||||
.message { background-color: #f8f9fa; border-left: 4px solid #007bff; padding: 15px; }
|
||||
code { background-color: #f1f1f1; padding: 2px 5px; border-radius: 3px; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>FastAPI Server Running</h1>
|
||||
<div class="message">
|
||||
<p>The FastAPI server is running correctly, but the frontend build files are not available.</p>
|
||||
<p>To see the frontend, make sure to build it first with <code>npm run build</code>.</p>
|
||||
<p>API endpoints are available at <a href="/docs">/docs</a>.</p>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
"""
|
||||
return HTMLResponse(content=html_content, status_code=200)
|
||||
0
backend/app/models/__init__.py
Normal file
0
backend/app/models/__init__.py
Normal file
12
backend/app/models/person.py
Normal file
12
backend/app/models/person.py
Normal file
@@ -0,0 +1,12 @@
|
||||
from sqlalchemy import Column, String
|
||||
from app.db.base_class import Base
|
||||
|
||||
|
||||
class Person(Base):
|
||||
"""
|
||||
Database model for a person
|
||||
"""
|
||||
id = Column(String, primary_key=True, index=True)
|
||||
firstName = Column(String, nullable=False)
|
||||
lastName = Column(String, nullable=False)
|
||||
linkedinUrl = Column(String, nullable=False)
|
||||
0
backend/app/schemas/__init__.py
Normal file
0
backend/app/schemas/__init__.py
Normal file
34
backend/app/schemas/person.py
Normal file
34
backend/app/schemas/person.py
Normal file
@@ -0,0 +1,34 @@
|
||||
from pydantic import BaseModel, Field, HttpUrl
|
||||
|
||||
|
||||
class PersonBase(BaseModel):
|
||||
"""
|
||||
Base schema for a person
|
||||
"""
|
||||
firstName: str
|
||||
lastName: str
|
||||
linkedinUrl: str
|
||||
|
||||
|
||||
class PersonCreate(PersonBase):
|
||||
"""
|
||||
Schema for creating a new person
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class PersonInDBBase(PersonBase):
|
||||
"""
|
||||
Base schema for a person in the database
|
||||
"""
|
||||
id: str
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
|
||||
class Person(PersonInDBBase):
|
||||
"""
|
||||
Schema for returning a person
|
||||
"""
|
||||
pass
|
||||
8
backend/main.py
Normal file
8
backend/main.py
Normal file
@@ -0,0 +1,8 @@
|
||||
import uvicorn
|
||||
import os
|
||||
|
||||
if __name__ == "__main__":
|
||||
# Use environment variables or default values
|
||||
host = os.getenv("HOST", "0.0.0.0")
|
||||
port = int(os.getenv("PORT", "8080"))
|
||||
uvicorn.run("app.main:app", host=host, port=port, reload=True)
|
||||
7
backend/requirements.txt
Normal file
7
backend/requirements.txt
Normal file
@@ -0,0 +1,7 @@
|
||||
fastapi==0.110.0
|
||||
uvicorn==0.29.0
|
||||
python-dotenv==1.0.1
|
||||
pydantic==2.6.1
|
||||
sqlalchemy==2.0.27
|
||||
uuid==1.30
|
||||
pydantic-settings==2.1.0
|
||||
51
backend/test_api.py
Normal file
51
backend/test_api.py
Normal file
@@ -0,0 +1,51 @@
|
||||
import requests
|
||||
import json
|
||||
import time
|
||||
|
||||
# Base URL for the API
|
||||
BASE_URL = "http://localhost:8080/api"
|
||||
|
||||
def test_get_people():
|
||||
"""Test the GET /api/people endpoint"""
|
||||
response = requests.get(f"{BASE_URL}/people")
|
||||
print("GET /api/people")
|
||||
print(f"Status Code: {response.status_code}")
|
||||
print(f"Response: {json.dumps(response.json(), indent=2)}")
|
||||
print("-" * 50)
|
||||
return response.json()
|
||||
|
||||
def test_create_person():
|
||||
"""Test the POST /api/people endpoint"""
|
||||
new_person = {
|
||||
"firstName": "Alan",
|
||||
"lastName": "Turing",
|
||||
"linkedinUrl": "https://www.linkedin.com/in/alan-turing"
|
||||
}
|
||||
response = requests.post(f"{BASE_URL}/people", json=new_person)
|
||||
print("POST /api/people")
|
||||
print(f"Status Code: {response.status_code}")
|
||||
print(f"Response: {json.dumps(response.json(), indent=2)}")
|
||||
print("-" * 50)
|
||||
return response.json()
|
||||
|
||||
def run_tests():
|
||||
"""Run all tests"""
|
||||
print("Testing API endpoints...")
|
||||
print("=" * 50)
|
||||
|
||||
# Test GET /api/people
|
||||
people = test_get_people()
|
||||
|
||||
# Test POST /api/people
|
||||
new_person = test_create_person()
|
||||
|
||||
# Test GET /api/people again to see the new person
|
||||
updated_people = test_get_people()
|
||||
|
||||
print("Tests completed!")
|
||||
|
||||
if __name__ == "__main__":
|
||||
# Wait a bit for the server to start
|
||||
print("Waiting for server to start...")
|
||||
time.sleep(2)
|
||||
run_tests()
|
||||
Reference in New Issue
Block a user