Enhancing documentation
This commit is contained in:
132
Makefile
132
Makefile
@@ -1,5 +1,5 @@
|
||||
# Comprehensive Makefile for LetzShop API (Windows Compatible)
|
||||
.PHONY: install install-test install-dev install-all dev test test-unit test-integration test-coverage test-fast test-slow test-auth test-products test-stock lint format check docker-build docker-up docker-down migrate clean check-tools setup setup-test
|
||||
.PHONY: install install-test install-dev install-docs install-all dev test test-unit test-integration test-coverage test-fast test-slow test-auth test-products test-stock lint format check docker-build docker-up docker-down migrate clean check-tools setup setup-test docs docs-serve docs-build docs-deploy docs-clean help
|
||||
|
||||
# Check if required tools are installed (Windows compatible)
|
||||
check-tools:
|
||||
@@ -8,6 +8,9 @@ check-tools:
|
||||
@where flake8 >nul 2>&1 || (echo flake8 is required but not installed. Run 'make install-dev' first. && exit 1)
|
||||
@where mypy >nul 2>&1 || (echo mypy is required but not installed. Run 'make install-dev' first. && exit 1)
|
||||
|
||||
check-docs-tools:
|
||||
@where mkdocs >nul 2>&1 || (echo mkdocs is required but not installed. Run 'make install-docs' first. && exit 1)
|
||||
|
||||
# Development setup
|
||||
install:
|
||||
pip install -r requirements.txt
|
||||
@@ -19,14 +22,68 @@ install-dev:
|
||||
pip install -r requirements.txt
|
||||
pip install -r tests/requirements-test.txt
|
||||
pip install -r requirements-dev.txt
|
||||
|
||||
install-docs:
|
||||
pip install -r requirements-docs.txt
|
||||
|
||||
install-all: install install-test install-dev
|
||||
install-all: install install-test install-dev install-docs
|
||||
|
||||
# Development server
|
||||
# Development servers
|
||||
dev:
|
||||
uvicorn main:app --reload --host 0.0.0.0 --port 8000
|
||||
|
||||
dev-with-docs:
|
||||
@echo Starting API server and documentation server...
|
||||
@start /B uvicorn main:app --reload --host 0.0.0.0 --port 8000
|
||||
@timeout /t 3 >nul
|
||||
@mkdocs serve --dev-addr=0.0.0.0:8001
|
||||
|
||||
# Documentation commands
|
||||
docs: docs-serve
|
||||
|
||||
docs-serve:
|
||||
@echo Starting MkDocs development server...
|
||||
mkdocs serve --dev-addr=0.0.0.0:8001
|
||||
|
||||
docs-build:
|
||||
@echo Building documentation site...
|
||||
mkdocs build --clean --strict
|
||||
|
||||
docs-build-quiet:
|
||||
@echo Building documentation (quiet)...
|
||||
mkdocs build --clean --quiet
|
||||
|
||||
docs-deploy:
|
||||
@echo Deploying documentation to GitHub Pages...
|
||||
mkdocs gh-deploy --clean
|
||||
|
||||
docs-deploy-force:
|
||||
@echo Force deploying documentation to GitHub Pages...
|
||||
mkdocs gh-deploy --clean --force
|
||||
|
||||
docs-update:
|
||||
@echo Updating documentation with API information...
|
||||
python scripts/update_docs.py
|
||||
|
||||
docs-clean:
|
||||
@echo Cleaning documentation build files...
|
||||
@if exist site rmdir /s /q site
|
||||
@echo Documentation build files cleaned!
|
||||
|
||||
docs-check:
|
||||
@echo Checking documentation for issues...
|
||||
mkdocs build --strict --verbose
|
||||
|
||||
docs-help:
|
||||
mkdocs --help
|
||||
|
||||
# Combined development environment
|
||||
dev-full: dev-with-docs
|
||||
@echo Development environment ready!
|
||||
@echo API server: http://localhost:8000
|
||||
@echo API docs: http://localhost:8000/docs
|
||||
@echo Documentation: http://localhost:8001
|
||||
|
||||
# Testing commands
|
||||
test:
|
||||
pytest tests/ -v
|
||||
@@ -117,6 +174,15 @@ deploy-staging:
|
||||
deploy-prod:
|
||||
docker-compose -f docker-compose.prod.yml up -d
|
||||
|
||||
# Documentation deployment workflow
|
||||
docs-publish: docs-build docs-deploy
|
||||
@echo Documentation published successfully!
|
||||
@echo Visit: https://yourusername.github.io/letzshop-import/
|
||||
|
||||
docs-preview: docs-build
|
||||
@echo Opening documentation preview...
|
||||
@start site\index.html
|
||||
|
||||
# Clean up (Windows compatible)
|
||||
clean:
|
||||
@if exist htmlcov rmdir /s /q htmlcov
|
||||
@@ -126,28 +192,63 @@ clean:
|
||||
@for /d /r . %%d in (__pycache__) do @if exist "%%d" rmdir /s /q "%%d"
|
||||
@del /s /q *.pyc 2>nul || echo No .pyc files found
|
||||
|
||||
clean-all: clean docs-clean
|
||||
@echo All build artifacts cleaned!
|
||||
|
||||
# Development workflow shortcuts
|
||||
setup: install-dev migrate-up
|
||||
setup: install-all migrate-up
|
||||
@echo Development environment setup complete!
|
||||
@echo Run 'make dev-full' to start both API and documentation servers
|
||||
|
||||
setup-test: install-test
|
||||
@echo Test environment setup complete!
|
||||
|
||||
setup-docs: install-docs
|
||||
@echo Documentation environment setup complete!
|
||||
@echo Run 'make docs-serve' to start documentation server
|
||||
|
||||
full-test: format lint test-coverage
|
||||
@echo Full test suite completed!
|
||||
|
||||
# Quality assurance workflow
|
||||
qa: format lint test-coverage docs-check
|
||||
@echo Quality assurance checks completed!
|
||||
|
||||
# Release workflow
|
||||
release-check: qa docs-build
|
||||
@echo Release readiness check completed!
|
||||
|
||||
# Help command
|
||||
help:
|
||||
@echo Available commands:
|
||||
@echo.
|
||||
@echo === SETUP ===
|
||||
@echo install - Install production dependencies
|
||||
@echo install-test - Install test dependencies only
|
||||
@echo install-dev - Install all development dependencies
|
||||
@echo install-docs - Install documentation dependencies
|
||||
@echo install-all - Install everything
|
||||
@echo setup - Complete development setup
|
||||
@echo setup-test - Setup test environment only
|
||||
@echo dev - Start development server
|
||||
@echo setup-docs - Setup documentation environment
|
||||
@echo.
|
||||
@echo Testing:
|
||||
@echo === DEVELOPMENT ===
|
||||
@echo dev - Start development server (API only)
|
||||
@echo dev-with-docs - Start both API and documentation servers
|
||||
@echo dev-full - Start full development environment with info
|
||||
@echo.
|
||||
@echo === DOCUMENTATION ===
|
||||
@echo docs - Start documentation development server (alias for docs-serve)
|
||||
@echo docs-serve - Start MkDocs development server
|
||||
@echo docs-build - Build documentation site
|
||||
@echo docs-deploy - Deploy documentation to GitHub Pages
|
||||
@echo docs-publish - Build and deploy documentation
|
||||
@echo docs-preview - Build and open documentation locally
|
||||
@echo docs-update - Update docs with API information
|
||||
@echo docs-check - Check documentation for issues
|
||||
@echo docs-clean - Clean documentation build files
|
||||
@echo.
|
||||
@echo === TESTING ===
|
||||
@echo test - Run all tests
|
||||
@echo test-unit - Run unit tests only
|
||||
@echo test-integration - Run integration tests only
|
||||
@@ -156,22 +257,33 @@ help:
|
||||
@echo test-slow - Run slow tests only
|
||||
@echo full-test - Format, lint, and test with coverage
|
||||
@echo.
|
||||
@echo Code Quality:
|
||||
@echo === CODE QUALITY ===
|
||||
@echo format - Format code with black and isort
|
||||
@echo lint - Run linting with flake8 and mypy
|
||||
@echo check - Format and lint code
|
||||
@echo ci - Full CI pipeline (format, lint, test)
|
||||
@echo qa - Quality assurance (format, lint, test, docs check)
|
||||
@echo.
|
||||
@echo Database:
|
||||
@echo === DATABASE ===
|
||||
@echo migrate-up - Run database migrations
|
||||
@echo migrate-down - Rollback last migration
|
||||
@echo migrate-reset - Reset and rerun all migrations
|
||||
@echo.
|
||||
@echo Docker:
|
||||
@echo === DOCKER ===
|
||||
@echo docker-build - Build Docker containers
|
||||
@echo docker-up - Start Docker containers
|
||||
@echo docker-down - Stop Docker containers
|
||||
@echo docker-restart - Restart Docker containers
|
||||
@echo.
|
||||
@echo Cleanup:
|
||||
@echo === CLEANUP ===
|
||||
@echo clean - Remove test artifacts and cache files
|
||||
@echo clean-all - Remove all build artifacts (including docs)
|
||||
@echo.
|
||||
@echo === WORKFLOWS ===
|
||||
@echo release-check - Complete release readiness check
|
||||
@echo.
|
||||
@echo === QUICK START ===
|
||||
@echo make setup # First time setup
|
||||
@echo make dev-full # Start development environment
|
||||
@echo make docs-serve # Start documentation server
|
||||
@echo make qa # Run quality checks
|
||||
@@ -10,7 +10,7 @@ from alembic import context
|
||||
sys.path.append(os.path.dirname(os.path.dirname(__file__)))
|
||||
|
||||
from app.core.config import settings
|
||||
from models.database import Base
|
||||
from models.database.base import Base
|
||||
|
||||
# Alembic Config object
|
||||
config = context.config
|
||||
|
||||
137
auth_example.py
137
auth_example.py
@@ -1,137 +0,0 @@
|
||||
# Authentication Usage Example
|
||||
# This file demonstrates how to use the authentication endpoints
|
||||
|
||||
import requests
|
||||
|
||||
# API Base URL
|
||||
BASE_URL = "http://localhost:8000"
|
||||
|
||||
|
||||
def register_user(email, username, password):
|
||||
"""Register a new user"""
|
||||
response = requests.post(
|
||||
f"{BASE_URL}/register",
|
||||
json={"email": email, "username": username, "password": password},
|
||||
)
|
||||
return response.json()
|
||||
|
||||
|
||||
def login_user(username, password):
|
||||
"""Login and get JWT token"""
|
||||
response = requests.post(
|
||||
f"{BASE_URL}/login", json={"username": username, "password": password}
|
||||
)
|
||||
if response.status_code == 200:
|
||||
data = response.json()
|
||||
return data["access_token"]
|
||||
else:
|
||||
print(f"Login failed: {response.json()}")
|
||||
return None
|
||||
|
||||
|
||||
def get_user_info(token):
|
||||
"""Get current user info"""
|
||||
headers = {"Authorization": f"Bearer {token}"}
|
||||
response = requests.get(f"{BASE_URL}/me", headers=headers)
|
||||
return response.json()
|
||||
|
||||
|
||||
def get_products(token, skip=0, limit=10):
|
||||
"""Get products (requires authentication)"""
|
||||
headers = {"Authorization": f"Bearer {token}"}
|
||||
response = requests.get(
|
||||
f"{BASE_URL}/products?skip={skip}&limit={limit}", headers=headers
|
||||
)
|
||||
return response.json()
|
||||
|
||||
|
||||
def create_product(token, product_data):
|
||||
"""Create a new product (requires authentication)"""
|
||||
headers = {"Authorization": f"Bearer {token}"}
|
||||
response = requests.post(f"{BASE_URL}/products", json=product_data, headers=headers)
|
||||
return response.json()
|
||||
|
||||
|
||||
# Example usage
|
||||
if __name__ == "__main__":
|
||||
# 1. Register a new user
|
||||
print("1. Registering new user...")
|
||||
try:
|
||||
user_result = register_user("test@example.com", "testuser", "password123")
|
||||
print(f"User registered: {user_result}")
|
||||
except Exception as e:
|
||||
print(f"Registration failed: {e}")
|
||||
|
||||
# 2. Login with default admin user
|
||||
print("\n2. Logging in as admin...")
|
||||
admin_token = login_user("admin", "admin123")
|
||||
if admin_token:
|
||||
print(f"Admin login successful! Token: {admin_token[:50]}...")
|
||||
|
||||
# 3. Get user info
|
||||
print("\n3. Getting admin user info...")
|
||||
user_info = get_user_info(admin_token)
|
||||
print(f"User info: {user_info}")
|
||||
|
||||
# 4. Create a sample product
|
||||
print("\n4. Creating a sample product...")
|
||||
sample_product = {
|
||||
"product_id": "TEST001",
|
||||
"title": "Test Product",
|
||||
"description": "A test product for demonstration",
|
||||
"price": "19.99",
|
||||
"brand": "Test Brand",
|
||||
"availability": "in stock",
|
||||
}
|
||||
|
||||
product_result = create_product(admin_token, sample_product)
|
||||
print(f"Product created: {product_result}")
|
||||
|
||||
# 5. Get products list
|
||||
print("\n5. Getting products list...")
|
||||
products = get_products(admin_token)
|
||||
print(f"Products: {products}")
|
||||
|
||||
# 6. Login with regular user
|
||||
print("\n6. Logging in as regular user...")
|
||||
user_token = login_user("testuser", "password123")
|
||||
if user_token:
|
||||
print(f"User login successful! Token: {user_token[:50]}...")
|
||||
|
||||
# Regular users can also access protected endpoints
|
||||
user_info = get_user_info(user_token)
|
||||
print(f"Regular user info: {user_info}")
|
||||
|
||||
products = get_products(user_token, limit=5)
|
||||
print(
|
||||
f"Products accessible to regular user: {len(products.get('products', []))} products"
|
||||
)
|
||||
|
||||
print("\nAuthentication example completed!")
|
||||
|
||||
# Example cURL commands:
|
||||
"""
|
||||
# Register a new user
|
||||
curl -X POST "http://localhost:8000/register" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"email": "user@example.com", "username": "newuser", "password": "password123"}'
|
||||
|
||||
# Login (get JWT token)
|
||||
curl -X POST "http://localhost:8000/login" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"username": "admin", "password": "admin123"}'
|
||||
|
||||
# Use token to access protected endpoint
|
||||
curl -X GET "http://localhost:8000/me" \
|
||||
-H "Authorization: Bearer YOUR_JWT_TOKEN_HERE"
|
||||
|
||||
# Get products (protected)
|
||||
curl -X GET "http://localhost:8000/products" \
|
||||
-H "Authorization: Bearer YOUR_JWT_TOKEN_HERE"
|
||||
|
||||
# Create product (protected)
|
||||
curl -X POST "http://localhost:8000/products" \
|
||||
-H "Authorization: Bearer YOUR_JWT_TOKEN_HERE" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"product_id": "TEST001", "title": "Test Product", "price": "19.99"}'
|
||||
"""
|
||||
@@ -1,493 +0,0 @@
|
||||
# Ecommerce Backend API with Marketplace Support
|
||||
|
||||
A comprehensive FastAPI-based product management system with JWT authentication, marketplace-aware CSV import/export, multi-shop support, and advanced stock management capabilities.
|
||||
|
||||
## Features
|
||||
|
||||
- **JWT Authentication** - Secure user registration, login, and role-based access control
|
||||
- **Marketplace Integration** - Support for multiple marketplaces (Letzshop, Amazon, eBay, Etsy, Shopify, etc.)
|
||||
- **Multi-Shop Management** - Shop creation, ownership validation, and product catalog management
|
||||
- **Advanced Product Management** - GTIN validation, price processing, and comprehensive filtering
|
||||
- **Stock Management** - Multi-location inventory tracking with add/remove/set operations
|
||||
- **CSV Import/Export** - Background processing of marketplace CSV files with progress tracking
|
||||
- **Rate Limiting** - Built-in request rate limiting for API protection
|
||||
- **Admin Panel** - Administrative functions for user and shop management
|
||||
- **Statistics & Analytics** - Comprehensive reporting on products, marketplaces, and inventory
|
||||
|
||||
## Technology Stack
|
||||
|
||||
- **FastAPI** - Modern, fast web framework for building APIs
|
||||
- **SQLAlchemy** - SQL toolkit and Object-Relational Mapping (ORM)
|
||||
- **PostgreSQL** - Primary database (SQLite supported for development)
|
||||
- **JWT** - JSON Web Tokens for secure authentication
|
||||
- **Pydantic** - Data validation using Python type annotations
|
||||
- **Pandas** - Data processing for CSV operations
|
||||
- **bcrypt** - Password hashing
|
||||
- **Pytest** - Comprehensive testing framework
|
||||
|
||||
## Directory Structure
|
||||
|
||||
```
|
||||
letzshop_api/
|
||||
├── main.py # FastAPI application entry point
|
||||
├── app/
|
||||
│ ├── core/
|
||||
│ │ ├── config.py # Configuration settings
|
||||
│ │ ├── database.py # Database setup
|
||||
│ │ └── lifespan.py # App lifecycle management
|
||||
│ ├── api/
|
||||
│ │ ├── deps.py # Common dependencies
|
||||
│ │ ├── main.py # API router setup
|
||||
│ │ └── v1/ # API version 1 routes
|
||||
│ │ ├── auth.py # Authentication endpoints
|
||||
│ │ ├── products.py # Product management
|
||||
│ │ ├── stock.py # Stock operations
|
||||
│ │ ├── shops.py # Shop management
|
||||
│ │ ├── marketplace.py # Marketplace imports
|
||||
│ │ ├── admin.py # Admin functions
|
||||
│ │ └── stats.py # Statistics
|
||||
│ ├── services/ # Business logic layer
|
||||
│ └── tasks/ # Background task processing
|
||||
├── models/
|
||||
│ ├── database_models.py # SQLAlchemy ORM models
|
||||
│ └── api_models.py # Pydantic API models
|
||||
├── utils/
|
||||
│ ├── data_processing.py # GTIN and price processing
|
||||
│ ├── csv_processor.py # CSV import/export
|
||||
│ └── database.py # Database utilities
|
||||
├── middleware/
|
||||
│ ├── auth.py # JWT authentication
|
||||
│ ├── rate_limiter.py # Rate limiting
|
||||
│ ├── error_handler.py # Error handling
|
||||
│ └── logging_middleware.py # Request logging
|
||||
├── tests/ # Comprehensive test suite
|
||||
└── requirements.txt # Dependencies
|
||||
```
|
||||
|
||||
## Quick Start
|
||||
|
||||
### 1. Installation
|
||||
|
||||
```bash
|
||||
# Clone the repository
|
||||
git clone <repository-url>
|
||||
cd letzshop_api
|
||||
|
||||
# Create virtual environment
|
||||
python -m venv venv
|
||||
source venv/bin/activate # On Windows: venv\Scripts\activate
|
||||
|
||||
# Install dependencies
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
### 2. Environment Configuration
|
||||
|
||||
Create a `.env` file in the project root:
|
||||
|
||||
```env
|
||||
# Database Configuration
|
||||
DATABASE_URL=postgresql://user:password@localhost/ecommerce
|
||||
# For development, you can use SQLite:
|
||||
# DATABASE_URL=sqlite:///./ecommerce.db
|
||||
|
||||
# JWT Configuration
|
||||
SECRET_KEY=your-super-secret-key-change-in-production
|
||||
ACCESS_TOKEN_EXPIRE_MINUTES=30
|
||||
|
||||
# API Configuration
|
||||
ALLOWED_HOSTS=["*"]
|
||||
RATE_LIMIT_REQUESTS=100
|
||||
RATE_LIMIT_WINDOW=3600
|
||||
|
||||
# Application Settings
|
||||
PROJECT_NAME="Ecommerce Backend API"
|
||||
VERSION="2.2.0"
|
||||
DEBUG=True
|
||||
```
|
||||
|
||||
### 3. Database Setup
|
||||
|
||||
```bash
|
||||
# The application will automatically create tables on startup
|
||||
# For production, consider using Alembic for migrations
|
||||
|
||||
# Install PostgreSQL (if using PostgreSQL)
|
||||
# Create database
|
||||
createdb ecommerce
|
||||
|
||||
# Run the application (tables will be created automatically)
|
||||
python main.py
|
||||
```
|
||||
|
||||
### 4. Running the Application
|
||||
|
||||
```bash
|
||||
# Development server
|
||||
python main.py
|
||||
|
||||
# Or using uvicorn directly
|
||||
uvicorn main:app --reload --host 0.0.0.0 --port 8000
|
||||
```
|
||||
|
||||
The API will be available at:
|
||||
- **API Documentation**: http://localhost:8000/docs (Swagger UI)
|
||||
- **Alternative Docs**: http://localhost:8000/redoc
|
||||
- **Health Check**: http://localhost:8000/health
|
||||
|
||||
## API Usage
|
||||
|
||||
### Authentication
|
||||
|
||||
#### Register a new user
|
||||
```bash
|
||||
curl -X POST "http://localhost:8000/api/v1/auth/register" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"email": "user@example.com",
|
||||
"username": "testuser",
|
||||
"password": "securepassword123"
|
||||
}'
|
||||
```
|
||||
|
||||
#### Login
|
||||
```bash
|
||||
curl -X POST "http://localhost:8000/api/v1/auth/login" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"username": "testuser",
|
||||
"password": "securepassword123"
|
||||
}'
|
||||
```
|
||||
|
||||
#### Use JWT Token
|
||||
```bash
|
||||
# Get token from login response and use in subsequent requests
|
||||
curl -X GET "http://localhost:8000/api/v1/product" \
|
||||
-H "Authorization: Bearer YOUR_JWT_TOKEN"
|
||||
```
|
||||
|
||||
### Product Management
|
||||
|
||||
#### Create a product
|
||||
```bash
|
||||
curl -X POST "http://localhost:8000/api/v1/product" \
|
||||
-H "Authorization: Bearer YOUR_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"product_id": "PROD001",
|
||||
"title": "Amazing Product",
|
||||
"description": "An amazing product description",
|
||||
"price": "29.99",
|
||||
"currency": "EUR",
|
||||
"brand": "BrandName",
|
||||
"gtin": "1234567890123",
|
||||
"availability": "in stock",
|
||||
"marketplace": "Letzshop",
|
||||
"shop_name": "MyShop"
|
||||
}'
|
||||
```
|
||||
|
||||
#### Get products with filtering
|
||||
```bash
|
||||
# Get all products
|
||||
curl -X GET "http://localhost:8000/api/v1/product" \
|
||||
-H "Authorization: Bearer YOUR_TOKEN"
|
||||
|
||||
# Filter by marketplace
|
||||
curl -X GET "http://localhost:8000/api/v1/product?marketplace=Amazon&limit=50" \
|
||||
-H "Authorization: Bearer YOUR_TOKEN"
|
||||
|
||||
# Search products
|
||||
curl -X GET "http://localhost:8000/api/v1/product?search=Amazing&brand=BrandName" \
|
||||
-H "Authorization: Bearer YOUR_TOKEN"
|
||||
```
|
||||
|
||||
### Stock Management
|
||||
|
||||
#### Set stock quantity
|
||||
```bash
|
||||
curl -X POST "http://localhost:8000/api/v1/stock" \
|
||||
-H "Authorization: Bearer YOUR_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"gtin": "1234567890123",
|
||||
"location": "WAREHOUSE_A",
|
||||
"quantity": 100
|
||||
}'
|
||||
```
|
||||
|
||||
#### Add stock
|
||||
```bash
|
||||
curl -X POST "http://localhost:8000/api/v1/stock/add" \
|
||||
-H "Authorization: Bearer YOUR_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"gtin": "1234567890123",
|
||||
"location": "WAREHOUSE_A",
|
||||
"quantity": 25
|
||||
}'
|
||||
```
|
||||
|
||||
#### Check stock levels
|
||||
```bash
|
||||
curl -X GET "http://localhost:8000/api/v1/stock/1234567890123" \
|
||||
-H "Authorization: Bearer YOUR_TOKEN"
|
||||
```
|
||||
|
||||
### Marketplace Import
|
||||
|
||||
#### Import products from CSV
|
||||
```bash
|
||||
curl -X POST "http://localhost:8000/api/v1/marketplace/import-product" \
|
||||
-H "Authorization: Bearer YOUR_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"url": "https://example.com/products.csv",
|
||||
"marketplace": "Amazon",
|
||||
"shop_code": "MYSHOP",
|
||||
"batch_size": 1000
|
||||
}'
|
||||
```
|
||||
|
||||
#### Check import status
|
||||
```bash
|
||||
curl -X GET "http://localhost:8000/api/v1/marketplace/import-status/1" \
|
||||
-H "Authorization: Bearer YOUR_TOKEN"
|
||||
```
|
||||
|
||||
### Export Data
|
||||
|
||||
#### Export products to CSV
|
||||
```bash
|
||||
# Export all products
|
||||
curl -X GET "http://localhost:8000/api/v1/export-csv" \
|
||||
-H "Authorization: Bearer YOUR_TOKEN" \
|
||||
-o products_export.csv
|
||||
|
||||
# Export with filters
|
||||
curl -X GET "http://localhost:8000/api/v1/export-csv?marketplace=Amazon&shop_name=MyShop" \
|
||||
-H "Authorization: Bearer YOUR_TOKEN" \
|
||||
-o amazon_products.csv
|
||||
```
|
||||
|
||||
## CSV Import Format
|
||||
|
||||
The system supports CSV imports with the following headers:
|
||||
|
||||
### Required Fields
|
||||
- `product_id` - Unique product identifier
|
||||
- `title` - Product title
|
||||
|
||||
### Optional Fields
|
||||
- `description` - Product description
|
||||
- `link` - Product URL
|
||||
- `image_link` - Product image URL
|
||||
- `availability` - Stock availability (in stock, out of stock, preorder)
|
||||
- `price` - Product price
|
||||
- `currency` - Price currency (EUR, USD, etc.)
|
||||
- `brand` - Product brand
|
||||
- `gtin` - Global Trade Item Number (EAN/UPC)
|
||||
- `google_product_category` - Product category
|
||||
- `marketplace` - Source marketplace
|
||||
- `shop_name` - Shop/seller name
|
||||
|
||||
### Example CSV
|
||||
```csv
|
||||
product_id,title,description,price,currency,brand,gtin,marketplace,shop_name
|
||||
PROD001,"Amazing Widget","The best widget ever",29.99,EUR,WidgetCorp,1234567890123,Letzshop,MyShop
|
||||
PROD002,"Super Gadget","A fantastic gadget",19.99,EUR,GadgetInc,9876543210987,Amazon,TechStore
|
||||
```
|
||||
|
||||
## Testing
|
||||
|
||||
### Run Tests
|
||||
```bash
|
||||
# Install test dependencies
|
||||
pip install -r tests/requirements_test.txt
|
||||
|
||||
# Run all tests
|
||||
pytest tests/ -v
|
||||
|
||||
# Run with coverage report
|
||||
pytest tests/ --cov=app --cov=models --cov=utils --cov-report=html
|
||||
|
||||
# Run specific test categories
|
||||
pytest tests/ -m unit -v # Unit tests only
|
||||
pytest tests/ -m integration -v # Integration tests only
|
||||
pytest tests/ -m "not slow" -v # Fast tests only
|
||||
|
||||
# Run specific test files
|
||||
pytest tests/test_authentication_endpoints.py -v # Authentication tests
|
||||
pytest tests/test_product_endpoints.py -v # Product tests
|
||||
pytest tests/test_stock_endpoints.py -v # Stock management tests
|
||||
```
|
||||
|
||||
### Test Coverage
|
||||
The test suite includes:
|
||||
- **Unit Tests** - Individual component testing
|
||||
- **Integration Tests** - Full workflow testing
|
||||
- **Security Tests** - Authentication, authorization, input validation
|
||||
- **Performance Tests** - Load testing with large datasets
|
||||
- **Error Handling Tests** - Edge cases and error conditions
|
||||
|
||||
## API Reference
|
||||
|
||||
### Authentication Endpoints
|
||||
- `POST /api/v1/auth/register` - Register new user
|
||||
- `POST /api/v1/auth/login` - Login user
|
||||
- `GET /api/v1/auth/me` - Get current user info
|
||||
|
||||
### Product Endpoints
|
||||
- `GET /api/v1/product` - List products with filtering
|
||||
- `POST /api/v1/product` - Create new product
|
||||
- `GET /api/v1/product/{product_id}` - Get specific product
|
||||
- `PUT /api/v1/product/{product_id}` - Update product
|
||||
- `DELETE /api/v1/product/{product_id}` - Delete product
|
||||
|
||||
### Stock Endpoints
|
||||
- `POST /api/v1/stock` - Set stock quantity
|
||||
- `POST /api/v1/stock/add` - Add to stock
|
||||
- `POST /api/v1/stock/remove` - Remove from stock
|
||||
- `GET /api/v1/stock/{gtin}` - Get stock by GTIN
|
||||
- `GET /api/v1/stock/{gtin}/total` - Get total stock
|
||||
- `GET /api/v1/stock` - List all stock entries
|
||||
|
||||
### Shop Endpoints
|
||||
- `POST /api/v1/shop` - Create new shop
|
||||
- `GET /api/v1/shop` - List shops
|
||||
- `GET /api/v1/shop/{shop_code}` - Get specific shop
|
||||
|
||||
### Marketplace Endpoints
|
||||
- `POST /api/v1/marketplace/import-product` - Start CSV import
|
||||
- `GET /api/v1/marketplace/import-status/{job_id}` - Check import status
|
||||
- `GET /api/v1/marketplace/import-jobs` - List import jobs
|
||||
|
||||
### Statistics Endpoints
|
||||
- `GET /api/v1/stats` - Get general statistics
|
||||
- `GET /api/v1/stats/marketplace-stats` - Get marketplace statistics
|
||||
|
||||
### Admin Endpoints (Admin only)
|
||||
- `GET /api/v1/admin/users` - List all users
|
||||
- `PUT /api/v1/admin/users/{user_id}/status` - Toggle user status
|
||||
- `GET /api/v1/admin/shops` - List all shops
|
||||
- `PUT /api/v1/admin/shops/{shop_id}/verify` - Verify/unverify shop
|
||||
|
||||
## Database Schema
|
||||
|
||||
### Core Tables
|
||||
- **users** - User accounts and authentication
|
||||
- **products** - Product catalog with marketplace info
|
||||
- **stock** - Inventory tracking by location and GTIN
|
||||
- **shops** - Shop/seller information
|
||||
- **shop_products** - Shop-specific product settings
|
||||
- **marketplace_import_jobs** - Background import job tracking
|
||||
|
||||
### Key Relationships
|
||||
- Users own shops (one-to-many)
|
||||
- Products belong to marketplaces and shops
|
||||
- Stock entries are linked to products via GTIN
|
||||
- Import jobs track user-initiated imports
|
||||
|
||||
## Security Features
|
||||
|
||||
- **JWT Authentication** - Secure token-based authentication
|
||||
- **Password Hashing** - bcrypt for secure password storage
|
||||
- **Role-Based Access** - User and admin role separation
|
||||
- **Rate Limiting** - Protection against API abuse
|
||||
- **Input Validation** - Comprehensive data validation
|
||||
- **SQL Injection Protection** - Parameterized queries
|
||||
- **CORS Configuration** - Cross-origin request handling
|
||||
|
||||
## Performance Optimizations
|
||||
|
||||
- **Database Indexing** - Strategic indexes on key columns
|
||||
- **Pagination** - Efficient data retrieval with skip/limit
|
||||
- **Streaming Responses** - Memory-efficient CSV exports
|
||||
- **Background Processing** - Async import job handling
|
||||
- **Connection Pooling** - Efficient database connections
|
||||
- **Query Optimization** - Optimized database queries
|
||||
|
||||
## Deployment
|
||||
|
||||
### Docker Deployment
|
||||
```dockerfile
|
||||
FROM python:3.11
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY requirements.txt .
|
||||
RUN pip install -r requirements.txt
|
||||
|
||||
COPY . .
|
||||
|
||||
EXPOSE 8000
|
||||
|
||||
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
|
||||
```
|
||||
|
||||
### Production Considerations
|
||||
- Use PostgreSQL for production database
|
||||
- Set strong SECRET_KEY in environment
|
||||
- Configure proper CORS settings
|
||||
- Enable HTTPS
|
||||
- Set up monitoring and logging
|
||||
- Use a reverse proxy (nginx)
|
||||
- Configure database connection pooling
|
||||
- Set up backup strategies
|
||||
|
||||
## Contributing
|
||||
|
||||
1. Fork the repository
|
||||
2. Create a feature branch
|
||||
3. Make your changes
|
||||
4. Add tests for new functionality
|
||||
5. Ensure all tests pass
|
||||
6. Submit a pull request
|
||||
|
||||
### Development Setup
|
||||
```bash
|
||||
# Install development dependencies
|
||||
pip install -r requirements_dev.txt
|
||||
|
||||
# Run pre-commit hooks
|
||||
pre-commit install
|
||||
|
||||
# Run linting
|
||||
flake8 app/ models/ utils/
|
||||
black app/ models/ utils/
|
||||
|
||||
# Run type checking
|
||||
mypy app/
|
||||
```
|
||||
|
||||
## Support & Documentation
|
||||
|
||||
- **API Documentation**: http://localhost:8000/docs
|
||||
- **Health Check**: http://localhost:8000/health
|
||||
- **Version Info**: http://localhost:8000/
|
||||
|
||||
For issues and feature requests, please create an issue in the repository.
|
||||
|
||||
## License
|
||||
|
||||
[Specify your license here]
|
||||
|
||||
## Changelog
|
||||
|
||||
### v2.2.0
|
||||
- Added marketplace-aware product import
|
||||
- Implemented multi-shop support
|
||||
- Enhanced stock management with location tracking
|
||||
- Added comprehensive test suite
|
||||
- Improved authentication and authorization
|
||||
|
||||
### v2.1.0
|
||||
- Added JWT authentication
|
||||
- Implemented role-based access control
|
||||
- Added CSV import/export functionality
|
||||
|
||||
### v2.0.0
|
||||
- Complete rewrite with FastAPI
|
||||
- Added PostgreSQL support
|
||||
- Implemented comprehensive API documentation
|
||||
377
docs/guides/user-management.md
Normal file
377
docs/guides/user-management.md
Normal file
@@ -0,0 +1,377 @@
|
||||
# Authentication Usage Examples
|
||||
|
||||
This document provides practical examples of how to use the authentication system in the Ecommerce Backend API.
|
||||
|
||||
## Overview
|
||||
|
||||
The API uses JWT (JSON Web Token) authentication with the following workflow:
|
||||
1. Register a new user account
|
||||
2. Login to receive a JWT token
|
||||
3. Include the token in the `Authorization` header for protected endpoints
|
||||
4. Token expires after 30 minutes (configurable)
|
||||
|
||||
## Python Examples
|
||||
|
||||
### Basic Authentication Functions
|
||||
|
||||
```python
|
||||
import requests
|
||||
|
||||
# API Base URL
|
||||
BASE_URL = "http://localhost:8000"
|
||||
|
||||
def register_user(email, username, password):
|
||||
"""Register a new user"""
|
||||
response = requests.post(
|
||||
f"{BASE_URL}/register",
|
||||
json={"email": email, "username": username, "password": password},
|
||||
)
|
||||
return response.json()
|
||||
|
||||
def login_user(username, password):
|
||||
"""Login and get JWT token"""
|
||||
response = requests.post(
|
||||
f"{BASE_URL}/login", json={"username": username, "password": password}
|
||||
)
|
||||
if response.status_code == 200:
|
||||
data = response.json()
|
||||
return data["access_token"]
|
||||
else:
|
||||
print(f"Login failed: {response.json()}")
|
||||
return None
|
||||
|
||||
def get_user_info(token):
|
||||
"""Get current user info"""
|
||||
headers = {"Authorization": f"Bearer {token}"}
|
||||
response = requests.get(f"{BASE_URL}/me", headers=headers)
|
||||
return response.json()
|
||||
```
|
||||
|
||||
### Using Protected Endpoints
|
||||
|
||||
```python
|
||||
def get_products(token, skip=0, limit=10):
|
||||
"""Get products (requires authentication)"""
|
||||
headers = {"Authorization": f"Bearer {token}"}
|
||||
response = requests.get(
|
||||
f"{BASE_URL}/products?skip={skip}&limit={limit}", headers=headers
|
||||
)
|
||||
return response.json()
|
||||
|
||||
def create_product(token, product_data):
|
||||
"""Create a new product (requires authentication)"""
|
||||
headers = {"Authorization": f"Bearer {token}"}
|
||||
response = requests.post(f"{BASE_URL}/products", json=product_data, headers=headers)
|
||||
return response.json()
|
||||
```
|
||||
|
||||
### Complete Workflow Example
|
||||
|
||||
```python
|
||||
# Example usage workflow
|
||||
if __name__ == "__main__":
|
||||
# 1. Register a new user
|
||||
print("1. Registering new user...")
|
||||
try:
|
||||
user_result = register_user("test@example.com", "testuser", "password123")
|
||||
print(f"User registered: {user_result}")
|
||||
except Exception as e:
|
||||
print(f"Registration failed: {e}")
|
||||
|
||||
# 2. Login with default admin user
|
||||
print("\n2. Logging in as admin...")
|
||||
admin_token = login_user("admin", "admin123")
|
||||
if admin_token:
|
||||
print(f"Admin login successful! Token: {admin_token[:50]}...")
|
||||
|
||||
# 3. Get user info
|
||||
print("\n3. Getting admin user info...")
|
||||
user_info = get_user_info(admin_token)
|
||||
print(f"User info: {user_info}")
|
||||
|
||||
# 4. Create a sample product
|
||||
print("\n4. Creating a sample product...")
|
||||
sample_product = {
|
||||
"product_id": "TEST001",
|
||||
"title": "Test Product",
|
||||
"description": "A test product for demonstration",
|
||||
"price": "19.99",
|
||||
"brand": "Test Brand",
|
||||
"availability": "in stock",
|
||||
}
|
||||
|
||||
product_result = create_product(admin_token, sample_product)
|
||||
print(f"Product created: {product_result}")
|
||||
|
||||
# 5. Get products list
|
||||
print("\n5. Getting products list...")
|
||||
products = get_products(admin_token)
|
||||
print(f"Products: {products}")
|
||||
|
||||
# 6. Login with regular user
|
||||
print("\n6. Logging in as regular user...")
|
||||
user_token = login_user("testuser", "password123")
|
||||
if user_token:
|
||||
print(f"User login successful! Token: {user_token[:50]}...")
|
||||
|
||||
# Regular users can also access protected endpoints
|
||||
user_info = get_user_info(user_token)
|
||||
print(f"Regular user info: {user_info}")
|
||||
|
||||
products = get_products(user_token, limit=5)
|
||||
print(
|
||||
f"Products accessible to regular user: {len(products.get('products', []))} products"
|
||||
)
|
||||
|
||||
print("\nAuthentication example completed!")
|
||||
```
|
||||
|
||||
## cURL Examples
|
||||
|
||||
### Register a New User
|
||||
|
||||
```bash
|
||||
curl -X POST "http://localhost:8000/register" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"email": "user@example.com",
|
||||
"username": "newuser",
|
||||
"password": "password123"
|
||||
}'
|
||||
```
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"id": 2,
|
||||
"email": "user@example.com",
|
||||
"username": "newuser",
|
||||
"role": "user",
|
||||
"is_active": true,
|
||||
"created_at": "2024-01-15T10:30:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
### Login (Get JWT Token)
|
||||
|
||||
```bash
|
||||
curl -X POST "http://localhost:8000/login" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"username": "admin",
|
||||
"password": "admin123"
|
||||
}'
|
||||
```
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
|
||||
"token_type": "bearer",
|
||||
"expires_in": 1800,
|
||||
"user": {
|
||||
"id": 1,
|
||||
"username": "admin",
|
||||
"email": "admin@example.com",
|
||||
"role": "admin",
|
||||
"is_active": true
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Use Token for Protected Endpoints
|
||||
|
||||
#### Get Current User Info
|
||||
|
||||
```bash
|
||||
curl -X GET "http://localhost:8000/me" \
|
||||
-H "Authorization: Bearer YOUR_JWT_TOKEN_HERE"
|
||||
```
|
||||
|
||||
#### Get Products List
|
||||
|
||||
```bash
|
||||
curl -X GET "http://localhost:8000/products" \
|
||||
-H "Authorization: Bearer YOUR_JWT_TOKEN_HERE"
|
||||
```
|
||||
|
||||
#### Create a Product
|
||||
|
||||
```bash
|
||||
curl -X POST "http://localhost:8000/products" \
|
||||
-H "Authorization: Bearer YOUR_JWT_TOKEN_HERE" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"product_id": "TEST001",
|
||||
"title": "Test Product",
|
||||
"description": "A test product for demonstration",
|
||||
"price": "19.99",
|
||||
"brand": "Test Brand",
|
||||
"availability": "in stock"
|
||||
}'
|
||||
```
|
||||
|
||||
## Default Accounts
|
||||
|
||||
### Admin Account
|
||||
- **Username**: `admin`
|
||||
- **Password**: `admin123`
|
||||
- **Email**: `admin@example.com`
|
||||
- **Role**: `admin`
|
||||
|
||||
**Security Note**: Change the admin password immediately in production environments!
|
||||
|
||||
## Authentication Headers
|
||||
|
||||
All protected endpoints require the JWT token in the Authorization header:
|
||||
|
||||
```
|
||||
Authorization: Bearer <your_jwt_token>
|
||||
```
|
||||
|
||||
## Error Handling
|
||||
|
||||
### Common Authentication Errors
|
||||
|
||||
#### Invalid Credentials (Login)
|
||||
```json
|
||||
{
|
||||
"detail": "Invalid credentials"
|
||||
}
|
||||
```
|
||||
|
||||
#### Missing or Invalid Token
|
||||
```json
|
||||
{
|
||||
"detail": "Not authenticated"
|
||||
}
|
||||
```
|
||||
|
||||
#### Expired Token
|
||||
```json
|
||||
{
|
||||
"detail": "Token has expired"
|
||||
}
|
||||
```
|
||||
|
||||
#### Insufficient Permissions
|
||||
```json
|
||||
{
|
||||
"detail": "Not enough permissions"
|
||||
}
|
||||
```
|
||||
|
||||
### Error Handling in Python
|
||||
|
||||
```python
|
||||
def safe_api_call(token, endpoint, method="GET", data=None):
|
||||
"""Make API call with proper error handling"""
|
||||
headers = {"Authorization": f"Bearer {token}"}
|
||||
|
||||
try:
|
||||
if method == "GET":
|
||||
response = requests.get(f"{BASE_URL}{endpoint}", headers=headers)
|
||||
elif method == "POST":
|
||||
response = requests.post(f"{BASE_URL}{endpoint}", json=data, headers=headers)
|
||||
|
||||
if response.status_code == 401:
|
||||
print("Authentication failed - token may be expired")
|
||||
return None
|
||||
elif response.status_code == 403:
|
||||
print("Access denied - insufficient permissions")
|
||||
return None
|
||||
elif response.status_code == 200:
|
||||
return response.json()
|
||||
else:
|
||||
print(f"API call failed: {response.status_code} - {response.text}")
|
||||
return None
|
||||
|
||||
except requests.exceptions.RequestException as e:
|
||||
print(f"Request failed: {e}")
|
||||
return None
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
### Token Management
|
||||
|
||||
1. **Store tokens securely** - Don't expose tokens in logs or client-side code
|
||||
2. **Handle token expiration** - Implement automatic token refresh or re-login
|
||||
3. **Use HTTPS in production** - Never send tokens over unencrypted connections
|
||||
|
||||
### Password Security
|
||||
|
||||
1. **Use strong passwords** - Minimum 6 characters (configurable)
|
||||
2. **Don't hardcode passwords** - Use environment variables or secure configuration
|
||||
3. **Regular password updates** - Especially for admin accounts
|
||||
|
||||
### API Integration
|
||||
|
||||
1. **Include error handling** - Always check response status codes
|
||||
2. **Implement retry logic** - Handle temporary network issues
|
||||
3. **Rate limiting awareness** - Respect API rate limits
|
||||
|
||||
## Testing Authentication
|
||||
|
||||
You can test the authentication system using the provided Python script or with make commands:
|
||||
|
||||
```bash
|
||||
# Run authentication tests
|
||||
make test-auth
|
||||
|
||||
# Run all tests including authentication
|
||||
make test
|
||||
```
|
||||
|
||||
## Integration with Frontend
|
||||
|
||||
### JavaScript/TypeScript Example
|
||||
|
||||
```javascript
|
||||
class APIClient {
|
||||
constructor(baseURL = 'http://localhost:8000') {
|
||||
this.baseURL = baseURL;
|
||||
this.token = localStorage.getItem('jwt_token');
|
||||
}
|
||||
|
||||
async login(username, password) {
|
||||
const response = await fetch(`${this.baseURL}/login`, {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify({username, password})
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
const data = await response.json();
|
||||
this.token = data.access_token;
|
||||
localStorage.setItem('jwt_token', this.token);
|
||||
return data;
|
||||
} else {
|
||||
throw new Error('Login failed');
|
||||
}
|
||||
}
|
||||
|
||||
async apiCall(endpoint, method = 'GET', data = null) {
|
||||
const headers = {
|
||||
'Authorization': `Bearer ${this.token}`,
|
||||
'Content-Type': 'application/json'
|
||||
};
|
||||
|
||||
const config = {method, headers};
|
||||
if (data) config.body = JSON.stringify(data);
|
||||
|
||||
const response = await fetch(`${this.baseURL}${endpoint}`, config);
|
||||
|
||||
if (response.status === 401) {
|
||||
// Token expired, redirect to login
|
||||
localStorage.removeItem('jwt_token');
|
||||
window.location.href = '/login';
|
||||
return;
|
||||
}
|
||||
|
||||
return response.json();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This authentication system provides secure access control for all API endpoints while maintaining ease of use for developers integrating with the system.
|
||||
@@ -1,299 +0,0 @@
|
||||
# Testing Overview
|
||||
|
||||
Our comprehensive test suite ensures code quality, reliability, and maintainability. This section covers everything you need to know about testing in the Letzshop Import project.
|
||||
|
||||
## Test Structure
|
||||
|
||||
We use a hierarchical test organization based on test types and scope:
|
||||
|
||||
```
|
||||
tests/
|
||||
├── conftest.py # Core test configuration and fixtures
|
||||
├── pytest.ini # Pytest configuration with custom markers
|
||||
├── fixtures/ # Shared test fixtures by domain
|
||||
├── unit/ # Fast, isolated component tests
|
||||
├── integration/ # Multi-component interaction tests
|
||||
├── performance/ # Performance and load tests
|
||||
├── system/ # End-to-end system behavior tests
|
||||
└── test_data/ # Test data files (CSV, JSON, etc.)
|
||||
```
|
||||
|
||||
## Test Types
|
||||
|
||||
### 🔧 Unit Tests
|
||||
**Purpose**: Test individual components in isolation
|
||||
**Speed**: Very fast (< 1 second each)
|
||||
**Scope**: Single function, method, or class
|
||||
|
||||
```bash
|
||||
# Run unit tests
|
||||
pytest -m unit
|
||||
```
|
||||
|
||||
**Examples**:
|
||||
- Data processing utilities
|
||||
- Model validation
|
||||
- Service business logic
|
||||
- Individual API endpoint handlers
|
||||
|
||||
### 🔗 Integration Tests
|
||||
**Purpose**: Test component interactions
|
||||
**Speed**: Fast to moderate (1-5 seconds each)
|
||||
**Scope**: Multiple components working together
|
||||
|
||||
```bash
|
||||
# Run integration tests
|
||||
pytest -m integration
|
||||
```
|
||||
|
||||
**Examples**:
|
||||
- API endpoints with database
|
||||
- Service layer interactions
|
||||
- Authentication workflows
|
||||
- File processing pipelines
|
||||
|
||||
### 🏗️ System Tests
|
||||
**Purpose**: Test complete application behavior
|
||||
**Speed**: Moderate (5-30 seconds each)
|
||||
**Scope**: End-to-end user scenarios
|
||||
|
||||
```bash
|
||||
# Run system tests
|
||||
pytest -m system
|
||||
```
|
||||
|
||||
**Examples**:
|
||||
- Complete user registration flow
|
||||
- Full CSV import process
|
||||
- Multi-step workflows
|
||||
- Error handling across layers
|
||||
|
||||
### ⚡ Performance Tests
|
||||
**Purpose**: Validate performance requirements
|
||||
**Speed**: Slow (30+ seconds each)
|
||||
**Scope**: Load, stress, and performance testing
|
||||
|
||||
```bash
|
||||
# Run performance tests
|
||||
pytest -m performance
|
||||
```
|
||||
|
||||
**Examples**:
|
||||
- API response times
|
||||
- Database query performance
|
||||
- Large file processing
|
||||
- Concurrent user scenarios
|
||||
|
||||
## Running Tests
|
||||
|
||||
### Basic Commands
|
||||
|
||||
```bash
|
||||
# Run all tests
|
||||
pytest
|
||||
|
||||
# Run with verbose output
|
||||
pytest -v
|
||||
|
||||
# Run specific test type
|
||||
pytest -m unit
|
||||
pytest -m integration
|
||||
pytest -m "unit or integration"
|
||||
|
||||
# Run tests in specific directory
|
||||
pytest tests/unit/
|
||||
pytest tests/integration/api/
|
||||
|
||||
# Run specific test file
|
||||
pytest tests/unit/services/test_product_service.py
|
||||
|
||||
# Run specific test class
|
||||
pytest tests/unit/services/test_product_service.py::TestProductService
|
||||
|
||||
# Run specific test method
|
||||
pytest tests/unit/services/test_product_service.py::TestProductService::test_create_product_success
|
||||
```
|
||||
|
||||
### Advanced Options
|
||||
|
||||
```bash
|
||||
# Run with coverage report
|
||||
pytest --cov=app --cov-report=html
|
||||
|
||||
# Run tests matching pattern
|
||||
pytest -k "product and not slow"
|
||||
|
||||
# Stop on first failure
|
||||
pytest -x
|
||||
|
||||
# Run failed tests from last run
|
||||
pytest --lf
|
||||
|
||||
# Run tests in parallel (if pytest-xdist installed)
|
||||
pytest -n auto
|
||||
|
||||
# Show slowest tests
|
||||
pytest --durations=10
|
||||
```
|
||||
|
||||
## Test Markers
|
||||
|
||||
We use pytest markers to categorize and selectively run tests:
|
||||
|
||||
| Marker | Purpose |
|
||||
|--------|---------|
|
||||
| `@pytest.mark.unit` | Fast, isolated component tests |
|
||||
| `@pytest.mark.integration` | Multi-component interaction tests |
|
||||
| `@pytest.mark.system` | End-to-end system tests |
|
||||
| `@pytest.mark.performance` | Performance and load tests |
|
||||
| `@pytest.mark.slow` | Long-running tests |
|
||||
| `@pytest.mark.api` | API endpoint tests |
|
||||
| `@pytest.mark.database` | Tests requiring database |
|
||||
| `@pytest.mark.auth` | Authentication/authorization tests |
|
||||
|
||||
### Example Usage
|
||||
|
||||
```python
|
||||
@pytest.mark.unit
|
||||
@pytest.mark.products
|
||||
class TestProductService:
|
||||
def test_create_product_success(self):
|
||||
# Test implementation
|
||||
pass
|
||||
|
||||
@pytest.mark.integration
|
||||
@pytest.mark.api
|
||||
@pytest.mark.database
|
||||
def test_product_creation_endpoint():
|
||||
# Test implementation
|
||||
pass
|
||||
```
|
||||
|
||||
## Test Configuration
|
||||
|
||||
### pytest.ini
|
||||
Our pytest configuration includes:
|
||||
|
||||
- **Custom markers** for test categorization
|
||||
- **Coverage settings** with 80% minimum threshold
|
||||
- **Test discovery** patterns and paths
|
||||
- **Output formatting** for better readability
|
||||
|
||||
### conftest.py
|
||||
Core test fixtures and configuration:
|
||||
|
||||
- **Database fixtures** for test isolation
|
||||
- **Authentication fixtures** for user/admin testing
|
||||
- **Client fixtures** for API testing
|
||||
- **Mock fixtures** for external dependencies
|
||||
|
||||
## Test Data Management
|
||||
|
||||
### Fixtures
|
||||
We organize fixtures by domain:
|
||||
|
||||
```python
|
||||
# tests/fixtures/product_fixtures.py
|
||||
@pytest.fixture
|
||||
def sample_product():
|
||||
return {
|
||||
"name": "Test Product",
|
||||
"gtin": "1234567890123",
|
||||
"price": "19.99"
|
||||
}
|
||||
|
||||
@pytest.fixture
|
||||
def product_factory():
|
||||
def _create_product(**kwargs):
|
||||
defaults = {"name": "Test", "gtin": "1234567890123"}
|
||||
defaults.update(kwargs)
|
||||
return defaults
|
||||
return _create_product
|
||||
```
|
||||
|
||||
### Test Data Files
|
||||
Static test data in `tests/test_data/`:
|
||||
|
||||
- CSV files for import testing
|
||||
- JSON files for API testing
|
||||
- Sample configuration files
|
||||
|
||||
## Coverage Requirements
|
||||
|
||||
We maintain high test coverage standards:
|
||||
|
||||
- **Minimum coverage**: 80% overall
|
||||
- **Critical paths**: 95%+ coverage required
|
||||
- **New code**: Must include tests
|
||||
- **HTML reports**: Generated in `htmlcov/`
|
||||
|
||||
```bash
|
||||
# Generate coverage report
|
||||
pytest --cov=app --cov-report=html --cov-report=term-missing
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
### Test Naming
|
||||
- Use descriptive test names that explain the scenario
|
||||
- Follow the pattern: `test_{action}_{scenario}_{expected_outcome}`
|
||||
- See our [Test Naming Conventions](test-naming-conventions.md) for details
|
||||
|
||||
### Test Structure
|
||||
- **Arrange**: Set up test data and conditions
|
||||
- **Act**: Execute the code being tested
|
||||
- **Assert**: Verify the expected outcome
|
||||
|
||||
```python
|
||||
def test_create_product_with_valid_data_returns_product(self):
|
||||
# Arrange
|
||||
product_data = {"name": "Test", "gtin": "1234567890123"}
|
||||
|
||||
# Act
|
||||
result = product_service.create_product(product_data)
|
||||
|
||||
# Assert
|
||||
assert result is not None
|
||||
assert result.name == "Test"
|
||||
```
|
||||
|
||||
### Test Isolation
|
||||
- Each test should be independent
|
||||
- Use database transactions that rollback
|
||||
- Mock external dependencies
|
||||
- Clean up test data
|
||||
|
||||
## Continuous Integration
|
||||
|
||||
Our CI pipeline runs:
|
||||
|
||||
1. **Linting** with flake8 and black
|
||||
2. **Type checking** with mypy
|
||||
3. **Security scanning** with bandit
|
||||
4. **Unit tests** on every commit
|
||||
5. **Integration tests** on pull requests
|
||||
6. **Performance tests** on releases
|
||||
|
||||
## Tools and Libraries
|
||||
|
||||
- **pytest**: Test framework and runner
|
||||
- **pytest-cov**: Coverage reporting
|
||||
- **pytest-asyncio**: Async test support
|
||||
- **pytest-mock**: Mocking utilities
|
||||
- **faker**: Test data generation
|
||||
- **httpx**: HTTP client for API testing
|
||||
- **factory-boy**: Test object factories
|
||||
|
||||
## Getting Started
|
||||
|
||||
1. **Read the conventions**: [Test Naming Conventions](test-naming-conventions.md)
|
||||
2. **Run existing tests**: `pytest -v`
|
||||
3. **Write your first test**: See examples in existing test files
|
||||
4. **Check coverage**: `pytest --cov`
|
||||
|
||||
## Need Help?
|
||||
|
||||
- **Examples**: Look at existing tests in `tests/` directory
|
||||
- **Documentation**: This testing section has detailed guides
|
||||
- **Issues**: Create a GitHub issue for testing questions
|
||||
- **Standards**: Follow our [naming conventions](test-naming-conventions.md)
|
||||
@@ -1,403 +0,0 @@
|
||||
# Running Tests
|
||||
|
||||
This guide covers everything you need to know about running tests in the Letzshop Import project.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
Ensure you have the test dependencies installed:
|
||||
|
||||
```bash
|
||||
pip install -r tests/requirements-test.txt
|
||||
```
|
||||
|
||||
Required packages:
|
||||
- `pytest>=7.4.0` - Test framework
|
||||
- `pytest-cov>=4.1.0` - Coverage reporting
|
||||
- `pytest-asyncio>=0.21.0` - Async test support
|
||||
- `pytest-mock>=3.11.0` - Mocking utilities
|
||||
- `httpx>=0.24.0` - HTTP client for API tests
|
||||
- `faker>=19.0.0` - Test data generation
|
||||
|
||||
## Basic Test Commands
|
||||
|
||||
### Run All Tests
|
||||
```bash
|
||||
# Run the entire test suite
|
||||
pytest
|
||||
|
||||
# Run with verbose output
|
||||
pytest -v
|
||||
|
||||
# Run with very verbose output (show individual test results)
|
||||
pytest -vv
|
||||
```
|
||||
|
||||
### Run by Test Type
|
||||
```bash
|
||||
# Run only unit tests (fast)
|
||||
pytest -m unit
|
||||
|
||||
# Run only integration tests
|
||||
pytest -m integration
|
||||
|
||||
# Run unit and integration tests
|
||||
pytest -m "unit or integration"
|
||||
|
||||
# Run everything except slow tests
|
||||
pytest -m "not slow"
|
||||
|
||||
# Run performance tests only
|
||||
pytest -m performance
|
||||
```
|
||||
|
||||
### Run by Directory
|
||||
```bash
|
||||
# Run all unit tests
|
||||
pytest tests/unit/
|
||||
|
||||
# Run all integration tests
|
||||
pytest tests/integration/
|
||||
|
||||
# Run API endpoint tests
|
||||
pytest tests/integration/api/
|
||||
|
||||
# Run service layer tests
|
||||
pytest tests/unit/services/
|
||||
```
|
||||
|
||||
### Run Specific Files
|
||||
```bash
|
||||
# Run specific test file
|
||||
pytest tests/unit/services/test_product_service.py
|
||||
|
||||
# Run multiple specific files
|
||||
pytest tests/unit/services/test_product_service.py tests/unit/utils/test_data_processing.py
|
||||
```
|
||||
|
||||
### Run Specific Tests
|
||||
```bash
|
||||
# Run specific test class
|
||||
pytest tests/unit/services/test_product_service.py::TestProductService
|
||||
|
||||
# Run specific test method
|
||||
pytest tests/unit/services/test_product_service.py::TestProductService::test_create_product_success
|
||||
|
||||
# Run tests matching pattern
|
||||
pytest -k "product and create"
|
||||
pytest -k "test_create_product"
|
||||
```
|
||||
|
||||
## Advanced Test Options
|
||||
|
||||
### Coverage Reporting
|
||||
```bash
|
||||
# Run with coverage report
|
||||
pytest --cov=app
|
||||
|
||||
# Coverage with missing lines
|
||||
pytest --cov=app --cov-report=term-missing
|
||||
|
||||
# Generate HTML coverage report
|
||||
pytest --cov=app --cov-report=html
|
||||
|
||||
# Coverage for specific modules
|
||||
pytest --cov=app.services --cov=app.api
|
||||
|
||||
# Fail if coverage below threshold
|
||||
pytest --cov=app --cov-fail-under=80
|
||||
```
|
||||
|
||||
### Output and Debugging
|
||||
```bash
|
||||
# Show local variables on failure
|
||||
pytest --tb=short --showlocals
|
||||
|
||||
# Stop on first failure
|
||||
pytest -x
|
||||
|
||||
# Stop after N failures
|
||||
pytest --maxfail=3
|
||||
|
||||
# Show print statements
|
||||
pytest -s
|
||||
|
||||
# Show warnings
|
||||
pytest -W error
|
||||
|
||||
# Capture output (default)
|
||||
pytest --capture=sys
|
||||
```
|
||||
|
||||
### Test Selection and Filtering
|
||||
```bash
|
||||
# Run only failed tests from last run
|
||||
pytest --lf
|
||||
|
||||
# Run failed tests first, then continue
|
||||
pytest --ff
|
||||
|
||||
# Run tests that match keyword expression
|
||||
pytest -k "user and not admin"
|
||||
|
||||
# Run tests modified since last commit
|
||||
pytest --testmon
|
||||
|
||||
# Collect tests without running
|
||||
pytest --collect-only
|
||||
```
|
||||
|
||||
### Performance and Parallel Execution
|
||||
```bash
|
||||
# Show 10 slowest tests
|
||||
pytest --durations=10
|
||||
|
||||
# Show all test durations
|
||||
pytest --durations=0
|
||||
|
||||
# Run tests in parallel (requires pytest-xdist)
|
||||
pytest -n auto
|
||||
pytest -n 4 # Use 4 workers
|
||||
```
|
||||
|
||||
## Test Environment Setup
|
||||
|
||||
### Database Tests
|
||||
```bash
|
||||
# Run database tests (uses test database)
|
||||
pytest -m database
|
||||
|
||||
# Run with test database reset
|
||||
pytest --tb=short tests/integration/database/
|
||||
```
|
||||
|
||||
### API Tests
|
||||
```bash
|
||||
# Run API endpoint tests
|
||||
pytest -m api
|
||||
|
||||
# Run with test client
|
||||
pytest tests/integration/api/ -v
|
||||
```
|
||||
|
||||
### Authentication Tests
|
||||
```bash
|
||||
# Run auth-related tests
|
||||
pytest -m auth
|
||||
|
||||
# Run with user fixtures
|
||||
pytest tests/integration/security/ -v
|
||||
```
|
||||
|
||||
## Configuration Options
|
||||
|
||||
### pytest.ini Settings
|
||||
Our `pytest.ini` is configured with:
|
||||
|
||||
```ini
|
||||
[pytest]
|
||||
# Test discovery
|
||||
testpaths = tests
|
||||
python_files = test_*.py
|
||||
python_classes = Test*
|
||||
python_functions = test_*
|
||||
|
||||
# Default options
|
||||
addopts =
|
||||
-v
|
||||
--tb=short
|
||||
--strict-markers
|
||||
--color=yes
|
||||
--durations=10
|
||||
--cov=app
|
||||
--cov-report=term-missing
|
||||
--cov-report=html:htmlcov
|
||||
--cov-fail-under=80
|
||||
|
||||
# Custom markers
|
||||
markers =
|
||||
unit: Unit tests
|
||||
integration: Integration tests
|
||||
# ... other markers
|
||||
```
|
||||
|
||||
### Environment Variables
|
||||
```bash
|
||||
# Set test environment
|
||||
export TESTING=true
|
||||
|
||||
# Database URL for tests (uses in-memory SQLite by default)
|
||||
export TEST_DATABASE_URL="sqlite:///:memory:"
|
||||
|
||||
# Disable external API calls in tests
|
||||
export MOCK_EXTERNAL_APIS=true
|
||||
```
|
||||
|
||||
## Common Test Scenarios
|
||||
|
||||
### Development Workflow
|
||||
```bash
|
||||
# Quick smoke test (unit tests only)
|
||||
pytest -m unit --maxfail=1
|
||||
|
||||
# Full test run before commit
|
||||
pytest -m "unit or integration"
|
||||
|
||||
# Pre-push comprehensive test
|
||||
pytest --cov=app --cov-fail-under=80
|
||||
```
|
||||
|
||||
### Debugging Failed Tests
|
||||
```bash
|
||||
# Run failed test with detailed output
|
||||
pytest --lf -vv --tb=long --showlocals
|
||||
|
||||
# Drop into debugger on failure (requires ipdb)
|
||||
pytest --pdb
|
||||
|
||||
# Run specific failing test in isolation
|
||||
pytest tests/path/to/test.py::TestClass::test_method -vv -s
|
||||
```
|
||||
|
||||
### Performance Testing
|
||||
```bash
|
||||
# Run performance tests only
|
||||
pytest -m performance --durations=0
|
||||
|
||||
# Run with performance profiling
|
||||
pytest -m performance --profile
|
||||
|
||||
# Load testing specific endpoints
|
||||
pytest tests/performance/test_api_performance.py -v
|
||||
```
|
||||
|
||||
### CI/CD Pipeline Tests
|
||||
```bash
|
||||
# Minimal test run (fast feedback)
|
||||
pytest -m "unit and not slow" --maxfail=5
|
||||
|
||||
# Full CI test run
|
||||
pytest --cov=app --cov-report=xml --junitxml=test-results.xml
|
||||
|
||||
# Security and integration tests
|
||||
pytest -m "security or integration" --tb=short
|
||||
```
|
||||
|
||||
## Test Reports and Output
|
||||
|
||||
### Coverage Reports
|
||||
```bash
|
||||
# Terminal coverage report
|
||||
pytest --cov=app --cov-report=term
|
||||
|
||||
# HTML coverage report (opens in browser)
|
||||
pytest --cov=app --cov-report=html
|
||||
open htmlcov/index.html
|
||||
|
||||
# XML coverage for CI
|
||||
pytest --cov=app --cov-report=xml
|
||||
```
|
||||
|
||||
### JUnit XML Reports
|
||||
```bash
|
||||
# Generate JUnit XML (for CI integration)
|
||||
pytest --junitxml=test-results.xml
|
||||
|
||||
# With coverage and JUnit
|
||||
pytest --cov=app --cov-report=xml --junitxml=test-results.xml
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Common Issues
|
||||
|
||||
#### Import Errors
|
||||
```bash
|
||||
# If getting import errors, ensure PYTHONPATH is set
|
||||
PYTHONPATH=. pytest
|
||||
|
||||
# Or install in development mode
|
||||
pip install -e .
|
||||
```
|
||||
|
||||
#### Database Connection Issues
|
||||
```bash
|
||||
# Check if test database is accessible
|
||||
python -c "from tests.conftest import engine; engine.connect()"
|
||||
|
||||
# Run with in-memory database
|
||||
pytest --override-db-url="sqlite:///:memory:"
|
||||
```
|
||||
|
||||
#### Fixture Not Found
|
||||
```bash
|
||||
# Ensure conftest.py is in the right location
|
||||
ls tests/conftest.py
|
||||
|
||||
# Check fixture imports
|
||||
pytest --fixtures tests/unit/
|
||||
```
|
||||
|
||||
#### Permission Issues
|
||||
```bash
|
||||
# Fix test file permissions
|
||||
chmod +x tests/**/*.py
|
||||
|
||||
# Clear pytest cache
|
||||
rm -rf .pytest_cache/
|
||||
```
|
||||
|
||||
### Performance Issues
|
||||
```bash
|
||||
# Identify slow tests
|
||||
pytest --durations=10 --durations-min=1.0
|
||||
|
||||
# Profile test execution
|
||||
pytest --profile-svg
|
||||
|
||||
# Run subset of tests for faster feedback
|
||||
pytest -m "unit and not slow"
|
||||
```
|
||||
|
||||
## Integration with IDEs
|
||||
|
||||
### VS Code
|
||||
Add to `.vscode/settings.json`:
|
||||
```json
|
||||
{
|
||||
"python.testing.pytestEnabled": true,
|
||||
"python.testing.pytestArgs": [
|
||||
"tests",
|
||||
"-v"
|
||||
],
|
||||
"python.testing.cwd": "${workspaceFolder}"
|
||||
}
|
||||
```
|
||||
|
||||
### PyCharm
|
||||
1. Go to Settings → Tools → Python Integrated Tools
|
||||
2. Set Testing → Default test runner to "pytest"
|
||||
3. Set pytest options: `-v --tb=short`
|
||||
|
||||
## Best Practices
|
||||
|
||||
### During Development
|
||||
1. **Run unit tests frequently** - Fast feedback loop
|
||||
2. **Run integration tests before commits** - Catch interaction issues
|
||||
3. **Check coverage regularly** - Ensure good test coverage
|
||||
4. **Use descriptive test names** - Easy to understand failures
|
||||
|
||||
### Before Code Review
|
||||
1. **Run full test suite** - `pytest`
|
||||
2. **Check coverage meets threshold** - `pytest --cov-fail-under=80`
|
||||
3. **Ensure no warnings** - `pytest -W error`
|
||||
4. **Test with fresh environment** - New terminal/clean cache
|
||||
|
||||
### In CI/CD
|
||||
1. **Fail fast on unit tests** - `pytest -m unit --maxfail=1`
|
||||
2. **Generate reports** - Coverage and JUnit XML
|
||||
3. **Run performance tests on schedule** - Not every commit
|
||||
4. **Archive test results** - For debugging and trends
|
||||
|
||||
---
|
||||
|
||||
Need help with a specific testing scenario? Check our [Testing FAQ](testing-faq.md) or open a GitHub issue!
|
||||
551
docs/testing/test-maintenance.md
Normal file
551
docs/testing/test-maintenance.md
Normal file
@@ -0,0 +1,551 @@
|
||||
# Test Maintenance Guide
|
||||
|
||||
This guide covers how to maintain, update, and contribute to our test suite as the application evolves. It's designed for developers who need to modify existing tests or add new test coverage.
|
||||
|
||||
## Test Maintenance Philosophy
|
||||
|
||||
Our test suite follows these core principles:
|
||||
- **Tests should be reliable** - They pass consistently and fail only when there are real issues
|
||||
- **Tests should be fast** - Unit tests complete in milliseconds, integration tests in seconds
|
||||
- **Tests should be maintainable** - Easy to update when code changes
|
||||
- **Tests should provide clear feedback** - Failures should clearly indicate what went wrong
|
||||
|
||||
## When to Update Tests
|
||||
|
||||
### Code Changes That Require Test Updates
|
||||
|
||||
**API Changes**:
|
||||
```bash
|
||||
# When you modify API endpoints, update integration tests
|
||||
pytest tests/integration/api/v1/test_*_endpoints.py -v
|
||||
```
|
||||
|
||||
**Business Logic Changes**:
|
||||
```bash
|
||||
# When you modify service logic, update unit tests
|
||||
pytest tests/unit/services/test_*_service.py -v
|
||||
```
|
||||
|
||||
**Database Model Changes**:
|
||||
```bash
|
||||
# When you modify models, update model tests
|
||||
pytest tests/unit/models/test_database_models.py -v
|
||||
```
|
||||
|
||||
**New Features**:
|
||||
- Add new test files following our naming conventions
|
||||
- Ensure both unit and integration test coverage
|
||||
- Add appropriate pytest markers
|
||||
|
||||
## Common Maintenance Tasks
|
||||
|
||||
### Adding Tests for New Features
|
||||
|
||||
**Step 1: Determine Test Type and Location**
|
||||
```python
|
||||
# New business logic → Unit test
|
||||
tests/unit/services/test_new_feature_service.py
|
||||
|
||||
# New API endpoint → Integration test
|
||||
tests/integration/api/v1/test_new_feature_endpoints.py
|
||||
|
||||
# New workflow → Integration test
|
||||
tests/integration/workflows/test_new_feature_workflow.py
|
||||
```
|
||||
|
||||
**Step 2: Create Test File with Proper Structure**
|
||||
```python
|
||||
import pytest
|
||||
from app.services.new_feature_service import NewFeatureService
|
||||
|
||||
@pytest.mark.unit
|
||||
@pytest.mark.new_feature # Add domain marker
|
||||
class TestNewFeatureService:
|
||||
"""Unit tests for NewFeatureService"""
|
||||
|
||||
def setup_method(self):
|
||||
"""Setup run before each test"""
|
||||
self.service = NewFeatureService()
|
||||
|
||||
def test_new_feature_with_valid_input_succeeds(self):
|
||||
"""Test the happy path"""
|
||||
# Test implementation
|
||||
pass
|
||||
|
||||
def test_new_feature_with_invalid_input_raises_error(self):
|
||||
"""Test error handling"""
|
||||
# Test implementation
|
||||
pass
|
||||
```
|
||||
|
||||
**Step 3: Add Domain Marker to pytest.ini**
|
||||
```ini
|
||||
# Add to markers section in pytest.ini
|
||||
new_feature: marks tests related to new feature functionality
|
||||
```
|
||||
|
||||
### Updating Tests for API Changes
|
||||
|
||||
**Example: Adding a new field to product creation**
|
||||
|
||||
```python
|
||||
# Before: tests/integration/api/v1/test_product_endpoints.py
|
||||
def test_create_product_success(self, client, auth_headers):
|
||||
product_data = {
|
||||
"product_id": "TEST001",
|
||||
"title": "Test Product",
|
||||
"price": "19.99"
|
||||
}
|
||||
response = client.post("/api/v1/product", json=product_data, headers=auth_headers)
|
||||
assert response.status_code == 200
|
||||
|
||||
# After: Adding 'category' field
|
||||
def test_create_product_success(self, client, auth_headers):
|
||||
product_data = {
|
||||
"product_id": "TEST001",
|
||||
"title": "Test Product",
|
||||
"price": "19.99",
|
||||
"category": "Electronics" # New field
|
||||
}
|
||||
response = client.post("/api/v1/product", json=product_data, headers=auth_headers)
|
||||
assert response.status_code == 200
|
||||
assert response.json()["category"] == "Electronics" # Verify new field
|
||||
|
||||
# Add test for validation
|
||||
def test_create_product_with_invalid_category_fails(self, client, auth_headers):
|
||||
product_data = {
|
||||
"product_id": "TEST002",
|
||||
"title": "Test Product",
|
||||
"price": "19.99",
|
||||
"category": "" # Invalid empty category
|
||||
}
|
||||
response = client.post("/api/v1/product", json=product_data, headers=auth_headers)
|
||||
assert response.status_code == 422
|
||||
```
|
||||
|
||||
### Updating Fixtures for Model Changes
|
||||
|
||||
**When you add fields to database models, update fixtures**:
|
||||
|
||||
```python
|
||||
# tests/fixtures/product_fixtures.py - Before
|
||||
@pytest.fixture
|
||||
def test_product(db):
|
||||
product = Product(
|
||||
product_id="TEST001",
|
||||
title="Test Product",
|
||||
price="10.99"
|
||||
)
|
||||
# ... rest of fixture
|
||||
|
||||
# After: Adding new category field
|
||||
@pytest.fixture
|
||||
def test_product(db):
|
||||
product = Product(
|
||||
product_id="TEST001",
|
||||
title="Test Product",
|
||||
price="10.99",
|
||||
category="Electronics" # Add new field with sensible default
|
||||
)
|
||||
# ... rest of fixture
|
||||
```
|
||||
|
||||
### Handling Breaking Changes
|
||||
|
||||
**When making breaking changes that affect many tests**:
|
||||
|
||||
1. **Update fixtures first** to include new required fields
|
||||
2. **Run tests to identify failures**: `pytest -x` (stop on first failure)
|
||||
3. **Update tests systematically** by domain
|
||||
4. **Verify coverage hasn't decreased**: `make test-coverage`
|
||||
|
||||
## Test Data Management
|
||||
|
||||
### Creating New Fixtures
|
||||
|
||||
**Add domain-specific fixtures to appropriate files**:
|
||||
|
||||
```python
|
||||
# tests/fixtures/new_domain_fixtures.py
|
||||
import pytest
|
||||
from models.database import NewModel
|
||||
|
||||
@pytest.fixture
|
||||
def test_new_model(db):
|
||||
"""Create a test instance of NewModel"""
|
||||
model = NewModel(
|
||||
name="Test Model",
|
||||
value="test_value"
|
||||
)
|
||||
db.add(model)
|
||||
db.commit()
|
||||
db.refresh(model)
|
||||
return model
|
||||
|
||||
@pytest.fixture
|
||||
def new_model_factory():
|
||||
"""Factory for creating custom NewModel instances"""
|
||||
def _create_new_model(db, **kwargs):
|
||||
defaults = {"name": "Default Name", "value": "default"}
|
||||
defaults.update(kwargs)
|
||||
model = NewModel(**defaults)
|
||||
db.add(model)
|
||||
db.commit()
|
||||
db.refresh(model)
|
||||
return model
|
||||
return _create_new_model
|
||||
```
|
||||
|
||||
**Register new fixture module in conftest.py**:
|
||||
```python
|
||||
# tests/conftest.py
|
||||
pytest_plugins = [
|
||||
"tests.fixtures.auth_fixtures",
|
||||
"tests.fixtures.product_fixtures",
|
||||
"tests.fixtures.shop_fixtures",
|
||||
"tests.fixtures.marketplace_fixtures",
|
||||
"tests.fixtures.new_domain_fixtures", # Add new fixture module
|
||||
]
|
||||
```
|
||||
|
||||
### Managing Test Data Files
|
||||
|
||||
**Static test data in tests/test_data/**:
|
||||
```
|
||||
tests/test_data/
|
||||
├── csv/
|
||||
│ ├── valid_products.csv # Standard valid product data
|
||||
│ ├── invalid_products.csv # Data with validation errors
|
||||
│ ├── large_product_set.csv # Performance testing data
|
||||
│ └── new_feature_data.csv # Data for new feature testing
|
||||
├── json/
|
||||
│ ├── api_responses.json # Mock API responses
|
||||
│ └── configuration_samples.json # Configuration test data
|
||||
└── fixtures/
|
||||
└── database_seeds.json # Database seed data
|
||||
```
|
||||
|
||||
**Update test data when adding new fields**:
|
||||
```csv
|
||||
# Before: tests/test_data/csv/valid_products.csv
|
||||
product_id,title,price
|
||||
TEST001,Product 1,19.99
|
||||
TEST002,Product 2,29.99
|
||||
|
||||
# After: Adding category field
|
||||
product_id,title,price,category
|
||||
TEST001,Product 1,19.99,Electronics
|
||||
TEST002,Product 2,29.99,Books
|
||||
```
|
||||
|
||||
## Performance Test Maintenance
|
||||
|
||||
### Updating Performance Baselines
|
||||
|
||||
**When application performance improves or requirements change**:
|
||||
|
||||
```python
|
||||
# tests/performance/test_api_performance.py
|
||||
def test_product_list_performance(self, client, auth_headers, db):
|
||||
# Create test data
|
||||
products = [Product(product_id=f"PERF{i:03d}") for i in range(100)]
|
||||
db.add_all(products)
|
||||
db.commit()
|
||||
|
||||
# Time the request
|
||||
start_time = time.time()
|
||||
response = client.get("/api/v1/product?limit=100", headers=auth_headers)
|
||||
end_time = time.time()
|
||||
|
||||
assert response.status_code == 200
|
||||
assert len(response.json()["products"]) == 100
|
||||
# Update baseline if performance has improved
|
||||
assert end_time - start_time < 1.5 # Previously was 2.0 seconds
|
||||
```
|
||||
|
||||
### Adding Performance Tests for New Features
|
||||
|
||||
```python
|
||||
@pytest.mark.performance
|
||||
@pytest.mark.slow
|
||||
@pytest.mark.new_feature
|
||||
def test_new_feature_performance_with_large_dataset(self, client, auth_headers, db):
|
||||
"""Test new feature performance with realistic data volume"""
|
||||
# Create large dataset
|
||||
large_dataset = [NewModel(data=f"item_{i}") for i in range(1000)]
|
||||
db.add_all(large_dataset)
|
||||
db.commit()
|
||||
|
||||
# Test performance
|
||||
start_time = time.time()
|
||||
response = client.post("/api/v1/new-feature/process",
|
||||
json={"process_all": True},
|
||||
headers=auth_headers)
|
||||
end_time = time.time()
|
||||
|
||||
assert response.status_code == 200
|
||||
assert end_time - start_time < 10.0 # Should complete within 10 seconds
|
||||
```
|
||||
|
||||
## Debugging and Troubleshooting
|
||||
|
||||
### Identifying Flaky Tests
|
||||
|
||||
**Tests that pass/fail inconsistently need attention**:
|
||||
|
||||
```bash
|
||||
# Run the same test multiple times to identify flaky behavior
|
||||
pytest tests/path/to/flaky_test.py -v --count=10
|
||||
|
||||
# Run with more verbose output to see what's changing
|
||||
pytest tests/path/to/flaky_test.py -vv --tb=long --showlocals
|
||||
```
|
||||
|
||||
**Common causes of flaky tests**:
|
||||
- Database state not properly cleaned between tests
|
||||
- Timing issues in async operations
|
||||
- External service dependencies
|
||||
- Shared mutable state between tests
|
||||
|
||||
### Fixing Common Test Issues
|
||||
|
||||
**Database State Issues**:
|
||||
```python
|
||||
# Ensure proper cleanup in fixtures
|
||||
@pytest.fixture
|
||||
def clean_database(db):
|
||||
"""Ensure clean database state"""
|
||||
yield db
|
||||
# Explicit cleanup if needed
|
||||
db.query(SomeModel).delete()
|
||||
db.commit()
|
||||
```
|
||||
|
||||
**Async Test Issues**:
|
||||
```python
|
||||
# Ensure proper async test setup
|
||||
@pytest.mark.asyncio
|
||||
async def test_async_operation():
|
||||
# Use await for all async operations
|
||||
result = await async_service.process_data()
|
||||
assert result is not None
|
||||
```
|
||||
|
||||
**Mock-Related Issues**:
|
||||
```python
|
||||
# Ensure mocks are properly reset between tests
|
||||
def setup_method(self):
|
||||
"""Reset mocks before each test"""
|
||||
self.mock_service.reset_mock()
|
||||
```
|
||||
|
||||
### Test Coverage Issues
|
||||
|
||||
**Identifying gaps in coverage**:
|
||||
```bash
|
||||
# Generate coverage report with missing lines
|
||||
pytest --cov=app --cov-report=term-missing
|
||||
|
||||
# View HTML report for detailed analysis
|
||||
pytest --cov=app --cov-report=html
|
||||
open htmlcov/index.html
|
||||
```
|
||||
|
||||
**Adding tests for uncovered code**:
|
||||
```python
|
||||
# Example: Adding test for error handling branch
|
||||
def test_service_method_handles_database_error(self, mock_db):
|
||||
"""Test error handling path that wasn't covered"""
|
||||
# Setup mock to raise exception
|
||||
mock_db.commit.side_effect = DatabaseError("Connection failed")
|
||||
|
||||
# Test that error is handled appropriately
|
||||
with pytest.raises(ServiceError):
|
||||
self.service.save_data(test_data)
|
||||
```
|
||||
|
||||
## Code Quality Standards
|
||||
|
||||
### Test Code Review Checklist
|
||||
|
||||
**Before submitting test changes**:
|
||||
- [ ] Tests have descriptive names explaining the scenario
|
||||
- [ ] Appropriate pytest markers are used
|
||||
- [ ] Test coverage hasn't decreased
|
||||
- [ ] Tests are in the correct category (unit/integration/system)
|
||||
- [ ] No hardcoded values that could break in different environments
|
||||
- [ ] Error cases are tested, not just happy paths
|
||||
- [ ] New fixtures are properly documented
|
||||
- [ ] Performance tests have reasonable baselines
|
||||
|
||||
### Refactoring Tests
|
||||
|
||||
**When refactoring test code**:
|
||||
```python
|
||||
# Before: Repetitive test setup
|
||||
class TestProductService:
|
||||
def test_create_product_success(self):
|
||||
service = ProductService()
|
||||
data = {"name": "Test", "price": "10.99"}
|
||||
result = service.create_product(data)
|
||||
assert result is not None
|
||||
|
||||
def test_create_product_validation_error(self):
|
||||
service = ProductService() # Duplicate setup
|
||||
data = {"name": "", "price": "invalid"}
|
||||
with pytest.raises(ValidationError):
|
||||
service.create_product(data)
|
||||
|
||||
# After: Using setup_method and constants
|
||||
class TestProductService:
|
||||
def setup_method(self):
|
||||
self.service = ProductService()
|
||||
self.valid_data = {"name": "Test", "price": "10.99"}
|
||||
|
||||
def test_create_product_success(self):
|
||||
result = self.service.create_product(self.valid_data)
|
||||
assert result is not None
|
||||
|
||||
def test_create_product_validation_error(self):
|
||||
invalid_data = {"name": "", "price": "invalid"}
|
||||
with pytest.raises(ValidationError):
|
||||
self.service.create_product(invalid_data)
|
||||
```
|
||||
|
||||
## Working with CI/CD
|
||||
|
||||
### Test Categories in CI Pipeline
|
||||
|
||||
Our CI pipeline runs tests in stages:
|
||||
|
||||
**Stage 1: Fast Feedback**
|
||||
```bash
|
||||
make test-fast # Unit tests + fast integration tests
|
||||
```
|
||||
|
||||
**Stage 2: Comprehensive Testing**
|
||||
```bash
|
||||
make test-coverage # Full suite with coverage
|
||||
```
|
||||
|
||||
**Stage 3: Performance Validation** (on release branches)
|
||||
```bash
|
||||
pytest -m performance
|
||||
```
|
||||
|
||||
### Making Tests CI-Friendly
|
||||
|
||||
**Ensure tests are deterministic**:
|
||||
```python
|
||||
# Bad: Tests that depend on current time
|
||||
def test_user_creation():
|
||||
user = create_user()
|
||||
assert user.created_at.day == datetime.now().day # Flaky at midnight
|
||||
|
||||
# Good: Tests with controlled time
|
||||
def test_user_creation(freezer):
|
||||
freezer.freeze("2024-01-15 10:00:00")
|
||||
user = create_user()
|
||||
assert user.created_at == datetime(2024, 1, 15, 10, 0, 0)
|
||||
```
|
||||
|
||||
**Make tests environment-independent**:
|
||||
```python
|
||||
# Use relative paths and environment variables
|
||||
TEST_DATA_DIR = Path(__file__).parent / "test_data"
|
||||
CSV_FILE = TEST_DATA_DIR / "sample_products.csv"
|
||||
```
|
||||
|
||||
## Migration and Upgrade Strategies
|
||||
|
||||
### When Upgrading Dependencies
|
||||
|
||||
**Test dependency upgrades**:
|
||||
```bash
|
||||
# Test with new versions before upgrading
|
||||
pip install pytest==8.0.0 pytest-cov==5.0.0
|
||||
make test
|
||||
|
||||
# If tests fail, identify compatibility issues
|
||||
pytest --tb=short -x
|
||||
```
|
||||
|
||||
**Update test configuration for new pytest versions**:
|
||||
```ini
|
||||
# pytest.ini - may need updates for new versions
|
||||
minversion = 8.0
|
||||
# Check if any deprecated features are used
|
||||
```
|
||||
|
||||
### Database Schema Changes
|
||||
|
||||
**When modifying database models**:
|
||||
1. Update model test fixtures first
|
||||
2. Run migration on test database
|
||||
3. Update affected test data files
|
||||
4. Run integration tests to catch relationship issues
|
||||
|
||||
```python
|
||||
# Update fixtures for new required fields
|
||||
@pytest.fixture
|
||||
def test_product(db):
|
||||
product = Product(
|
||||
# ... existing fields
|
||||
new_required_field="default_value" # Add with sensible default
|
||||
)
|
||||
return product
|
||||
```
|
||||
|
||||
## Documentation and Knowledge Sharing
|
||||
|
||||
### Documenting Complex Test Scenarios
|
||||
|
||||
**For complex business logic tests**:
|
||||
```python
|
||||
def test_complex_pricing_calculation_scenario(self):
|
||||
"""
|
||||
Test pricing calculation with multiple discounts and tax rules.
|
||||
|
||||
Scenario:
|
||||
- Product price: $100
|
||||
- Member discount: 10%
|
||||
- Seasonal discount: 5% (applied after member discount)
|
||||
- Tax rate: 8.5%
|
||||
|
||||
Expected calculation:
|
||||
Base: $100 → Member discount: $90 → Seasonal: $85.50 → Tax: $92.77
|
||||
"""
|
||||
# Test implementation with clear steps
|
||||
```
|
||||
|
||||
### Team Knowledge Sharing
|
||||
|
||||
**Maintain test documentation**:
|
||||
- Update this guide when adding new test patterns
|
||||
- Document complex fixture relationships
|
||||
- Share test debugging techniques in team meetings
|
||||
- Create examples for new team members
|
||||
|
||||
## Summary: Test Maintenance Best Practices
|
||||
|
||||
**Daily Practices**:
|
||||
- Run relevant tests before committing code
|
||||
- Add tests for new functionality immediately
|
||||
- Keep test names descriptive and current
|
||||
- Update fixtures when models change
|
||||
|
||||
**Regular Maintenance**:
|
||||
- Review and update performance baselines
|
||||
- Refactor repetitive test code
|
||||
- Clean up unused fixtures and test data
|
||||
- Monitor test execution times
|
||||
|
||||
**Long-term Strategy**:
|
||||
- Plan test architecture for new features
|
||||
- Evaluate test coverage trends
|
||||
- Update testing tools and practices
|
||||
- Share knowledge across the team
|
||||
|
||||
**Remember**: Good tests are living documentation of your system's behavior. Keep them current, clear, and comprehensive to maintain a healthy codebase.
|
||||
|
||||
Use this guide alongside the [Testing Guide](testing-guide.md) for complete test management knowledge.
|
||||
@@ -1,383 +0,0 @@
|
||||
# Test Naming Conventions
|
||||
|
||||
This document outlines the naming conventions used in our test suite to ensure consistency, clarity, and maintainability across the project.
|
||||
|
||||
## Overview
|
||||
|
||||
Our test suite follows a hierarchical structure organized by test type and component, with clear naming patterns that make it easy to locate and understand test purposes.
|
||||
|
||||
## Directory Structure
|
||||
|
||||
```
|
||||
tests/
|
||||
├── conftest.py # Core test configuration and fixtures
|
||||
├── pytest.ini # Pytest configuration
|
||||
├── fixtures/ # Shared test fixtures organized by domain
|
||||
├── unit/ # Fast, isolated component tests
|
||||
├── integration/ # Multi-component interaction tests
|
||||
├── performance/ # Performance and load tests
|
||||
├── system/ # End-to-end system behavior tests
|
||||
└── test_data/ # Test data files (CSV, JSON, etc.)
|
||||
```
|
||||
|
||||
## File Naming Conventions
|
||||
|
||||
### General Rule
|
||||
All test files must start with `test_` prefix for pytest auto-discovery.
|
||||
|
||||
**Pattern:** `test_{component/feature}.py`
|
||||
|
||||
### Unit Tests (`tests/unit/`)
|
||||
|
||||
Focus on testing individual components in isolation.
|
||||
|
||||
```
|
||||
tests/unit/
|
||||
├── models/
|
||||
│ ├── test_database_models.py # Database model tests
|
||||
│ ├── test_api_models.py # API model validation
|
||||
│ └── test_model_relationships.py # Model relationship tests
|
||||
├── utils/
|
||||
│ ├── test_data_processing.py # Data processing utilities
|
||||
│ ├── test_csv_processor.py # CSV processing utilities
|
||||
│ ├── test_validators.py # Validation utilities
|
||||
│ └── test_formatters.py # Data formatting utilities
|
||||
├── services/
|
||||
│ ├── test_admin_service.py # Admin service business logic
|
||||
│ ├── test_auth_service.py # Authentication service
|
||||
│ ├── test_product_service.py # Product management service
|
||||
│ ├── test_shop_service.py # Shop management service
|
||||
│ ├── test_stock_service.py # Stock management service
|
||||
│ ├── test_marketplace_service.py # Marketplace import service
|
||||
│ └── test_stats_service.py # Statistics service
|
||||
└── core/
|
||||
├── test_config.py # Configuration management
|
||||
├── test_database.py # Database utilities
|
||||
└── test_logging.py # Logging functionality
|
||||
```
|
||||
|
||||
### Integration Tests (`tests/integration/`)
|
||||
|
||||
Focus on testing interactions between multiple components.
|
||||
|
||||
```
|
||||
tests/integration/
|
||||
├── api/v1/
|
||||
│ ├── test_auth_endpoints.py # Authentication API endpoints
|
||||
│ ├── test_admin_endpoints.py # Admin API endpoints
|
||||
│ ├── test_product_endpoints.py # Product CRUD endpoints
|
||||
│ ├── test_shop_endpoints.py # Shop management endpoints
|
||||
│ ├── test_stock_endpoints.py # Stock management endpoints
|
||||
│ ├── test_marketplace_endpoints.py # Import endpoints
|
||||
│ ├── test_stats_endpoints.py # Statistics endpoints
|
||||
│ └── test_pagination.py # Pagination functionality
|
||||
├── database/
|
||||
│ ├── test_migrations.py # Database migration tests
|
||||
│ ├── test_transactions.py # Transaction handling
|
||||
│ └── test_constraints.py # Database constraints
|
||||
├── security/
|
||||
│ ├── test_authentication.py # Authentication mechanisms
|
||||
│ ├── test_authorization.py # Permission and role tests
|
||||
│ ├── test_input_validation.py # Input security validation
|
||||
│ ├── test_rate_limiting.py # Rate limiting middleware
|
||||
│ └── test_cors.py # CORS configuration
|
||||
└── workflows/
|
||||
├── test_product_import_workflow.py # Complete import process
|
||||
├── test_user_registration_flow.py # User registration process
|
||||
└── test_shop_setup_flow.py # Shop creation workflow
|
||||
```
|
||||
|
||||
### System Tests (`tests/system/`)
|
||||
|
||||
Focus on end-to-end application behavior.
|
||||
|
||||
```
|
||||
tests/system/
|
||||
├── test_application_startup.py # Application initialization
|
||||
├── test_error_handling.py # Global error handling
|
||||
├── test_health_checks.py # Health endpoint tests
|
||||
├── test_database_connectivity.py # Database connection tests
|
||||
└── test_external_dependencies.py # Third-party service tests
|
||||
```
|
||||
|
||||
### Performance Tests (`tests/performance/`)
|
||||
|
||||
Focus on performance benchmarks and load testing.
|
||||
|
||||
```
|
||||
tests/performance/
|
||||
├── test_api_performance.py # API endpoint performance
|
||||
├── test_database_performance.py # Database query performance
|
||||
├── test_import_performance.py # Large file import tests
|
||||
└── test_concurrent_users.py # Multi-user scenarios
|
||||
```
|
||||
|
||||
## Class Naming Conventions
|
||||
|
||||
Use descriptive class names that clearly indicate what is being tested:
|
||||
|
||||
### Pattern: `Test{ComponentName}{TestType}`
|
||||
|
||||
```python
|
||||
# Good examples
|
||||
class TestProductService: # Testing ProductService class
|
||||
class TestProductModel: # Testing Product model
|
||||
class TestProductEndpoints: # Testing product API endpoints
|
||||
class TestProductValidation: # Testing product validation logic
|
||||
class TestProductPermissions: # Testing product access control
|
||||
|
||||
# Avoid generic names
|
||||
class TestProduct: # Too vague - what aspect of Product?
|
||||
class Tests: # Too generic
|
||||
```
|
||||
|
||||
### Specialized Class Naming
|
||||
|
||||
```python
|
||||
class TestProductCRUD: # CRUD operations
|
||||
class TestProductSecurity: # Security-related tests
|
||||
class TestProductErrorHandling: # Error scenario tests
|
||||
class TestProductEdgeCases: # Boundary condition tests
|
||||
```
|
||||
|
||||
## Method Naming Conventions
|
||||
|
||||
Test method names should be descriptive and follow a clear pattern that explains:
|
||||
1. What is being tested
|
||||
2. The scenario/condition
|
||||
3. Expected outcome
|
||||
|
||||
### Pattern: `test_{action}_{scenario}_{expected_outcome}`
|
||||
|
||||
```python
|
||||
# Good examples - Clear and descriptive
|
||||
def test_create_product_with_valid_data_returns_product():
|
||||
def test_create_product_with_invalid_gtin_raises_validation_error():
|
||||
def test_get_product_by_id_when_not_found_returns_404():
|
||||
def test_update_product_without_permission_raises_forbidden():
|
||||
def test_delete_product_with_existing_stock_prevents_deletion():
|
||||
|
||||
# Acceptable shorter versions
|
||||
def test_create_product_success():
|
||||
def test_create_product_invalid_data():
|
||||
def test_create_product_unauthorized():
|
||||
def test_get_product_not_found():
|
||||
```
|
||||
|
||||
### Method Naming Patterns by Scenario
|
||||
|
||||
**Happy Path Tests:**
|
||||
```python
|
||||
def test_create_user_success():
|
||||
def test_login_valid_credentials():
|
||||
def test_import_csv_valid_format():
|
||||
```
|
||||
|
||||
**Error/Edge Case Tests:**
|
||||
```python
|
||||
def test_create_user_duplicate_email_fails():
|
||||
def test_login_invalid_credentials_rejected():
|
||||
def test_import_csv_malformed_data_raises_error():
|
||||
```
|
||||
|
||||
**Permission/Security Tests:**
|
||||
```python
|
||||
def test_admin_endpoint_requires_admin_role():
|
||||
def test_user_cannot_access_other_user_data():
|
||||
def test_api_key_required_for_marketplace_import():
|
||||
```
|
||||
|
||||
**Boundary/Edge Cases:**
|
||||
```python
|
||||
def test_pagination_with_zero_items():
|
||||
def test_price_with_maximum_decimal_places():
|
||||
def test_gtin_with_minimum_valid_length():
|
||||
```
|
||||
|
||||
## Marker Usage
|
||||
|
||||
Use pytest markers to categorize and run specific test types:
|
||||
|
||||
```python
|
||||
@pytest.mark.unit
|
||||
class TestProductService:
|
||||
"""Unit tests for ProductService business logic"""
|
||||
|
||||
@pytest.mark.integration
|
||||
@pytest.mark.api
|
||||
class TestProductEndpoints:
|
||||
"""Integration tests for Product API endpoints"""
|
||||
|
||||
@pytest.mark.slow
|
||||
@pytest.mark.performance
|
||||
class TestImportPerformance:
|
||||
"""Performance tests for large CSV imports"""
|
||||
```
|
||||
|
||||
### Available Markers
|
||||
|
||||
| Marker | Purpose |
|
||||
|--------|---------|
|
||||
| `@pytest.mark.unit` | Fast, isolated component tests |
|
||||
| `@pytest.mark.integration` | Multi-component interaction tests |
|
||||
| `@pytest.mark.system` | End-to-end system tests |
|
||||
| `@pytest.mark.performance` | Performance and load tests |
|
||||
| `@pytest.mark.slow` | Long-running tests |
|
||||
| `@pytest.mark.api` | API endpoint tests |
|
||||
| `@pytest.mark.database` | Tests requiring database |
|
||||
| `@pytest.mark.auth` | Authentication/authorization tests |
|
||||
| `@pytest.mark.admin` | Admin functionality tests |
|
||||
|
||||
## File Organization Best Practices
|
||||
|
||||
### Mirror Source Structure
|
||||
Test files should mirror the source code structure:
|
||||
|
||||
```
|
||||
app/services/product_service.py → tests/unit/services/test_product_service.py
|
||||
app/api/v1/admin.py → tests/integration/api/v1/test_admin_endpoints.py
|
||||
```
|
||||
|
||||
### Group Related Tests
|
||||
Keep related tests in the same file:
|
||||
|
||||
```python
|
||||
# test_product_service.py
|
||||
class TestProductCreation: # All product creation scenarios
|
||||
class TestProductValidation: # All validation scenarios
|
||||
class TestProductQueries: # All query scenarios
|
||||
```
|
||||
|
||||
### Separate Complex Workflows
|
||||
Break complex workflows into separate files:
|
||||
|
||||
```
|
||||
test_user_registration_flow.py # Complete registration process
|
||||
test_shop_setup_flow.py # Shop creation workflow
|
||||
test_product_import_workflow.py # CSV import end-to-end
|
||||
```
|
||||
|
||||
## Running Tests by Name Pattern
|
||||
|
||||
Use pytest's flexible test discovery:
|
||||
|
||||
```bash
|
||||
# Run all unit tests
|
||||
pytest tests/unit/ -v
|
||||
|
||||
# Run specific component tests
|
||||
pytest tests/ -k "product" -v
|
||||
|
||||
# Run by marker
|
||||
pytest -m "unit and not slow" -v
|
||||
|
||||
# Run specific test class
|
||||
pytest tests/unit/services/test_product_service.py::TestProductService -v
|
||||
|
||||
# Run specific test method
|
||||
pytest tests/unit/services/test_product_service.py::TestProductService::test_create_product_success -v
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
### Complete Test File Example
|
||||
|
||||
```python
|
||||
# tests/unit/services/test_product_service.py
|
||||
import pytest
|
||||
from unittest.mock import Mock, patch
|
||||
|
||||
from app.services.product_service import ProductService
|
||||
from app.models.database_models import Product
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
class TestProductService:
|
||||
"""Unit tests for ProductService business logic"""
|
||||
|
||||
def setup_method(self):
|
||||
"""Setup run before each test method"""
|
||||
self.service = ProductService()
|
||||
self.mock_db = Mock()
|
||||
|
||||
def test_create_product_with_valid_data_returns_product(self):
|
||||
"""Test successful product creation with valid input data"""
|
||||
# Arrange
|
||||
product_data = {
|
||||
"name": "Test Product",
|
||||
"gtin": "1234567890123",
|
||||
"price": "19.99"
|
||||
}
|
||||
|
||||
# Act
|
||||
result = self.service.create_product(product_data)
|
||||
|
||||
# Assert
|
||||
assert result is not None
|
||||
assert result.name == "Test Product"
|
||||
|
||||
def test_create_product_with_invalid_gtin_raises_validation_error(self):
|
||||
"""Test product creation fails with invalid GTIN format"""
|
||||
# Arrange
|
||||
product_data = {
|
||||
"name": "Test Product",
|
||||
"gtin": "invalid_gtin",
|
||||
"price": "19.99"
|
||||
}
|
||||
|
||||
# Act & Assert
|
||||
with pytest.raises(ValidationError, match="Invalid GTIN format"):
|
||||
self.service.create_product(product_data)
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
class TestProductValidation:
|
||||
"""Unit tests for product validation logic"""
|
||||
|
||||
def test_validate_gtin_with_valid_format_returns_true(self):
|
||||
"""Test GTIN validation accepts valid 13-digit GTIN"""
|
||||
assert ProductService.validate_gtin("1234567890123") is True
|
||||
|
||||
def test_validate_gtin_with_invalid_format_returns_false(self):
|
||||
"""Test GTIN validation rejects invalid format"""
|
||||
assert ProductService.validate_gtin("invalid") is False
|
||||
```
|
||||
|
||||
## Naming Anti-Patterns to Avoid
|
||||
|
||||
❌ **Avoid these patterns:**
|
||||
|
||||
```python
|
||||
# Too generic
|
||||
def test_product():
|
||||
def test_user():
|
||||
def test_api():
|
||||
|
||||
# Unclear purpose
|
||||
def test_function():
|
||||
def test_method():
|
||||
def test_endpoint():
|
||||
|
||||
# Non-descriptive
|
||||
def test_1():
|
||||
def test_a():
|
||||
def test_scenario():
|
||||
|
||||
# Inconsistent naming
|
||||
def testProductCreation(): # Wrong case
|
||||
def test_product_creation(): # Good
|
||||
def TestProductCreation(): # Wrong - this is for classes
|
||||
```
|
||||
|
||||
✅ **Use these patterns instead:**
|
||||
|
||||
```python
|
||||
# Clear, descriptive, consistent
|
||||
def test_create_product_with_valid_data_succeeds():
|
||||
def test_authenticate_user_with_invalid_token_fails():
|
||||
def test_get_products_endpoint_returns_paginated_results():
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
Following these naming conventions will help maintain a clean, understandable, and maintainable test suite that scales with your project.
|
||||
470
docs/testing/testing-guide.md
Normal file
470
docs/testing/testing-guide.md
Normal file
@@ -0,0 +1,470 @@
|
||||
# Testing Guide for Developers
|
||||
|
||||
This guide provides everything your development team needs to know about our comprehensive test suite structure, how to run tests effectively, and how to maintain test quality.
|
||||
|
||||
## Quick Start
|
||||
|
||||
```bash
|
||||
# Install test dependencies
|
||||
make install-test
|
||||
|
||||
# Run all tests
|
||||
make test
|
||||
|
||||
# Run fast tests only (development workflow)
|
||||
make test-fast
|
||||
|
||||
# Run with coverage
|
||||
make test-coverage
|
||||
```
|
||||
|
||||
## Test Structure Overview
|
||||
|
||||
Our test suite is organized hierarchically by test type and execution speed to optimize development workflows:
|
||||
|
||||
```
|
||||
tests/
|
||||
├── conftest.py # Core test configuration and database fixtures
|
||||
├── pytest.ini # Test configuration with markers and coverage
|
||||
├── fixtures/ # Domain-organized test fixtures
|
||||
│ ├── auth_fixtures.py # Users, tokens, authentication headers
|
||||
│ ├── product_fixtures.py # Products, factories, bulk test data
|
||||
│ ├── shop_fixtures.py # Shops, stock, shop-product relationships
|
||||
│ └── marketplace_fixtures.py # Import jobs and marketplace data
|
||||
├── unit/ # Fast, isolated component tests (< 1 second)
|
||||
│ ├── models/ # Database and API model tests
|
||||
│ ├── utils/ # Utility function tests
|
||||
│ ├── services/ # Business logic tests
|
||||
│ └── middleware/ # Middleware component tests
|
||||
├── integration/ # Multi-component tests (1-10 seconds)
|
||||
│ ├── api/v1/ # API endpoint tests with database
|
||||
│ ├── security/ # Authentication, authorization tests
|
||||
│ ├── tasks/ # Background task integration tests
|
||||
│ └── workflows/ # Multi-step process tests
|
||||
├── performance/ # Performance benchmarks (10+ seconds)
|
||||
│ └── test_api_performance.py # Load testing and benchmarks
|
||||
├── system/ # End-to-end system tests (30+ seconds)
|
||||
│ └── test_error_handling.py # Application-wide error handling
|
||||
└── test_data/ # Static test data files
|
||||
└── csv/sample_products.csv # Sample CSV for import testing
|
||||
```
|
||||
|
||||
## Test Categories and When to Use Each
|
||||
|
||||
### Unit Tests (`tests/unit/`)
|
||||
**Purpose**: Test individual components in isolation
|
||||
**Speed**: Very fast (< 1 second each)
|
||||
**Use when**: Testing business logic, data processing, model validation
|
||||
|
||||
```bash
|
||||
# Run during active development
|
||||
pytest -m unit
|
||||
|
||||
# Example locations:
|
||||
tests/unit/services/test_product_service.py # Business logic
|
||||
tests/unit/utils/test_data_processing.py # Utility functions
|
||||
tests/unit/models/test_database_models.py # Model validation
|
||||
```
|
||||
|
||||
### Integration Tests (`tests/integration/`)
|
||||
**Purpose**: Test component interactions
|
||||
**Speed**: Moderate (1-10 seconds each)
|
||||
**Use when**: Testing API endpoints, service interactions, workflows
|
||||
|
||||
```bash
|
||||
# Run before commits
|
||||
pytest -m integration
|
||||
|
||||
# Example locations:
|
||||
tests/integration/api/v1/test_admin_endpoints.py # API endpoints
|
||||
tests/integration/security/test_authentication.py # Auth workflows
|
||||
tests/integration/workflows/test_product_import.py # Multi-step processes
|
||||
```
|
||||
|
||||
### Performance Tests (`tests/performance/`)
|
||||
**Purpose**: Validate performance requirements
|
||||
**Speed**: Slow (10+ seconds each)
|
||||
**Use when**: Testing response times, load capacity, large data processing
|
||||
|
||||
```bash
|
||||
# Run periodically or in CI
|
||||
pytest -m performance
|
||||
```
|
||||
|
||||
### System Tests (`tests/system/`)
|
||||
**Purpose**: End-to-end application behavior
|
||||
**Speed**: Slowest (30+ seconds each)
|
||||
**Use when**: Testing complete user scenarios, error handling across layers
|
||||
|
||||
```bash
|
||||
# Run before releases
|
||||
pytest -m system
|
||||
```
|
||||
|
||||
## Daily Development Workflow
|
||||
|
||||
### During Active Development
|
||||
```bash
|
||||
# Quick feedback loop - run relevant unit tests
|
||||
pytest tests/unit/services/test_product_service.py -v
|
||||
|
||||
# Test specific functionality you're working on
|
||||
pytest -k "product and create" -m unit
|
||||
|
||||
# Fast comprehensive check
|
||||
make test-fast # Equivalent to: pytest -m "not slow"
|
||||
```
|
||||
|
||||
### Before Committing Code
|
||||
```bash
|
||||
# Run unit and integration tests
|
||||
make test-unit
|
||||
make test-integration
|
||||
|
||||
# Or run both with coverage
|
||||
make test-coverage
|
||||
```
|
||||
|
||||
### Before Creating Pull Request
|
||||
```bash
|
||||
# Full test suite with linting
|
||||
make ci # Runs format, lint, and test-coverage
|
||||
|
||||
# Check if all tests pass
|
||||
make test
|
||||
```
|
||||
|
||||
## Running Specific Tests
|
||||
|
||||
### By Test Type
|
||||
```bash
|
||||
# Fast unit tests only
|
||||
pytest -m unit
|
||||
|
||||
# Integration tests only
|
||||
pytest -m integration
|
||||
|
||||
# Everything except slow tests
|
||||
pytest -m "not slow"
|
||||
|
||||
# Database-dependent tests
|
||||
pytest -m database
|
||||
|
||||
# Authentication-related tests
|
||||
pytest -m auth
|
||||
```
|
||||
|
||||
### By Component/Domain
|
||||
```bash
|
||||
# All product-related tests
|
||||
pytest -k "product"
|
||||
|
||||
# Admin functionality tests
|
||||
pytest -m admin
|
||||
|
||||
# API endpoint tests
|
||||
pytest -m api
|
||||
|
||||
# All tests in a directory
|
||||
pytest tests/unit/services/ -v
|
||||
```
|
||||
|
||||
### By Specific Files or Methods
|
||||
```bash
|
||||
# Specific test file
|
||||
pytest tests/unit/services/test_product_service.py -v
|
||||
|
||||
# Specific test class
|
||||
pytest tests/unit/services/test_product_service.py::TestProductService -v
|
||||
|
||||
# Specific test method
|
||||
pytest tests/unit/services/test_product_service.py::TestProductService::test_create_product_success -v
|
||||
```
|
||||
|
||||
## Test Fixtures and Data
|
||||
|
||||
### Using Existing Fixtures
|
||||
Our fixtures are organized by domain in the `fixtures/` directory:
|
||||
|
||||
```python
|
||||
# In your test file
|
||||
def test_product_creation(test_user, test_shop, auth_headers):
|
||||
"""Uses auth_fixtures.py fixtures"""
|
||||
# test_user: Creates a test user
|
||||
# test_shop: Creates a test shop owned by test_user
|
||||
# auth_headers: Provides authentication headers for API calls
|
||||
|
||||
def test_multiple_products(multiple_products):
|
||||
"""Uses product_fixtures.py fixtures"""
|
||||
# multiple_products: Creates 5 test products with different attributes
|
||||
assert len(multiple_products) == 5
|
||||
|
||||
def test_with_factory(product_factory, db):
|
||||
"""Uses factory fixtures for custom test data"""
|
||||
# Create custom product with specific attributes
|
||||
product = product_factory(db, title="Custom Product", price="99.99")
|
||||
assert product.title == "Custom Product"
|
||||
```
|
||||
|
||||
### Available Fixtures by Domain
|
||||
|
||||
**Authentication (`auth_fixtures.py`)**:
|
||||
- `test_user`, `test_admin`, `other_user`
|
||||
- `auth_headers`, `admin_headers`
|
||||
- `auth_manager`
|
||||
|
||||
**Products (`product_fixtures.py`)**:
|
||||
- `test_product`, `unique_product`, `multiple_products`
|
||||
- `product_factory` (for custom products)
|
||||
|
||||
**Shops (`shop_fixtures.py`)**:
|
||||
- `test_shop`, `unique_shop`, `inactive_shop`, `verified_shop`
|
||||
- `shop_product`, `test_stock`, `multiple_stocks`
|
||||
- `shop_factory` (for custom shops)
|
||||
|
||||
**Marketplace (`marketplace_fixtures.py`)**:
|
||||
- `test_marketplace_job`
|
||||
|
||||
## Writing New Tests
|
||||
|
||||
### Test File Location
|
||||
Choose location based on what you're testing:
|
||||
|
||||
```python
|
||||
# Business logic → unit tests
|
||||
tests/unit/services/test_my_new_service.py
|
||||
|
||||
# API endpoints → integration tests
|
||||
tests/integration/api/v1/test_my_new_endpoints.py
|
||||
|
||||
# Multi-component workflows → integration tests
|
||||
tests/integration/workflows/test_my_new_workflow.py
|
||||
|
||||
# Performance concerns → performance tests
|
||||
tests/performance/test_my_performance.py
|
||||
```
|
||||
|
||||
### Test Class Structure
|
||||
```python
|
||||
import pytest
|
||||
from app.services.my_service import MyService
|
||||
|
||||
@pytest.mark.unit # Always add appropriate markers
|
||||
@pytest.mark.products # Domain-specific marker
|
||||
class TestMyService:
|
||||
"""Test suite for MyService business logic"""
|
||||
|
||||
def setup_method(self):
|
||||
"""Run before each test method"""
|
||||
self.service = MyService()
|
||||
|
||||
def test_create_item_with_valid_data_succeeds(self):
|
||||
"""Test successful item creation - descriptive name explaining scenario"""
|
||||
# Arrange
|
||||
item_data = {"name": "Test Item", "price": "10.99"}
|
||||
|
||||
# Act
|
||||
result = self.service.create_item(item_data)
|
||||
|
||||
# Assert
|
||||
assert result is not None
|
||||
assert result.name == "Test Item"
|
||||
|
||||
def test_create_item_with_invalid_data_raises_validation_error(self):
|
||||
"""Test validation error handling"""
|
||||
# Arrange
|
||||
invalid_data = {"name": "", "price": "invalid"}
|
||||
|
||||
# Act & Assert
|
||||
with pytest.raises(ValidationError):
|
||||
self.service.create_item(invalid_data)
|
||||
```
|
||||
|
||||
### API Integration Test Example
|
||||
```python
|
||||
import pytest
|
||||
|
||||
@pytest.mark.integration
|
||||
@pytest.mark.api
|
||||
@pytest.mark.products
|
||||
class TestProductEndpoints:
|
||||
"""Integration tests for product API endpoints"""
|
||||
|
||||
def test_create_product_endpoint_success(self, client, auth_headers):
|
||||
"""Test successful product creation via API"""
|
||||
# Arrange
|
||||
product_data = {
|
||||
"product_id": "TEST001",
|
||||
"title": "Test Product",
|
||||
"price": "19.99"
|
||||
}
|
||||
|
||||
# Act
|
||||
response = client.post("/api/v1/product",
|
||||
json=product_data,
|
||||
headers=auth_headers)
|
||||
|
||||
# Assert
|
||||
assert response.status_code == 200
|
||||
assert response.json()["product_id"] == "TEST001"
|
||||
```
|
||||
|
||||
## Test Naming Conventions
|
||||
|
||||
### Files
|
||||
- `test_{component_name}.py` for the file name
|
||||
- Mirror your source structure: `app/services/product.py` → `tests/unit/services/test_product_service.py`
|
||||
|
||||
### Classes
|
||||
- `TestComponentName` for the main component
|
||||
- `TestComponentValidation` for validation-specific tests
|
||||
- `TestComponentErrorHandling` for error scenarios
|
||||
|
||||
### Methods
|
||||
Use descriptive names that explain the scenario:
|
||||
```python
|
||||
# Good - explains what, when, and expected outcome
|
||||
def test_create_product_with_valid_data_returns_product(self):
|
||||
def test_create_product_with_duplicate_id_raises_error(self):
|
||||
def test_get_product_when_not_found_returns_404(self):
|
||||
|
||||
# Acceptable shorter versions
|
||||
def test_create_product_success(self):
|
||||
def test_create_product_validation_error(self):
|
||||
def test_get_product_not_found(self):
|
||||
```
|
||||
|
||||
## Coverage Requirements
|
||||
|
||||
We maintain high coverage standards:
|
||||
- **Minimum overall coverage**: 80%
|
||||
- **New code coverage**: 90%+
|
||||
- **Critical paths**: 95%+
|
||||
|
||||
```bash
|
||||
# Check coverage
|
||||
make test-coverage
|
||||
|
||||
# View detailed HTML report
|
||||
open htmlcov/index.html
|
||||
|
||||
# Fail build if coverage too low
|
||||
pytest --cov=app --cov-fail-under=80
|
||||
```
|
||||
|
||||
## Debugging Failed Tests
|
||||
|
||||
### Get Detailed Information
|
||||
```bash
|
||||
# Verbose output with local variables
|
||||
pytest tests/path/to/test.py -vv --tb=long --showlocals
|
||||
|
||||
# Stop on first failure
|
||||
pytest -x
|
||||
|
||||
# Re-run only failed tests
|
||||
pytest --lf
|
||||
```
|
||||
|
||||
### Common Issues and Solutions
|
||||
|
||||
**Import Errors**:
|
||||
```bash
|
||||
# Ensure you're in project root and have installed in dev mode
|
||||
pip install -e .
|
||||
PYTHONPATH=. pytest
|
||||
```
|
||||
|
||||
**Database Issues**:
|
||||
```bash
|
||||
# Tests use in-memory SQLite by default
|
||||
# Check if fixtures are properly imported
|
||||
pytest --fixtures tests/
|
||||
```
|
||||
|
||||
**Fixture Not Found**:
|
||||
```bash
|
||||
# Ensure fixture modules are listed in conftest.py pytest_plugins
|
||||
# Check fixture dependencies (test_shop needs test_user)
|
||||
```
|
||||
|
||||
## Performance and Optimization
|
||||
|
||||
### Speed Up Test Runs
|
||||
```bash
|
||||
# Run in parallel (install pytest-xdist first)
|
||||
pytest -n auto
|
||||
|
||||
# Skip slow tests during development
|
||||
pytest -m "not slow"
|
||||
|
||||
# Run only changed tests (install pytest-testmon)
|
||||
pytest --testmon
|
||||
```
|
||||
|
||||
### Find Slow Tests
|
||||
```bash
|
||||
# Show 10 slowest tests
|
||||
pytest --durations=10
|
||||
|
||||
# Show all test durations
|
||||
pytest --durations=0
|
||||
```
|
||||
|
||||
## Continuous Integration Integration
|
||||
|
||||
Our tests integrate with CI/CD pipelines through make targets:
|
||||
|
||||
```bash
|
||||
# Commands used in CI
|
||||
make ci # Format, lint, test with coverage
|
||||
make test-fast # Quick feedback in early CI stages
|
||||
make test-coverage # Full test run with coverage reporting
|
||||
```
|
||||
|
||||
The CI pipeline:
|
||||
1. Runs `make test-fast` for quick feedback
|
||||
2. Runs `make ci` for comprehensive checks
|
||||
3. Generates coverage reports in XML format
|
||||
4. Uploads coverage to reporting tools
|
||||
|
||||
## Best Practices Summary
|
||||
|
||||
### DO:
|
||||
- Write tests for new code before committing
|
||||
- Use descriptive test names explaining the scenario
|
||||
- Keep unit tests fast (< 1 second each)
|
||||
- Use appropriate fixtures for test data
|
||||
- Add proper pytest markers to categorize tests
|
||||
- Test both happy path and error scenarios
|
||||
- Maintain good test coverage (80%+)
|
||||
|
||||
### DON'T:
|
||||
- Write tests that depend on external services (use mocks)
|
||||
- Create tests that depend on execution order
|
||||
- Use hardcoded values that might change
|
||||
- Write overly complex test setups
|
||||
- Ignore failing tests
|
||||
- Skip adding tests for bug fixes
|
||||
|
||||
## Getting Help
|
||||
|
||||
- **Examples**: Look at existing tests in similar components
|
||||
- **Fixtures**: Check `tests/fixtures/` for available test data
|
||||
- **Configuration**: See `pytest.ini` for available markers
|
||||
- **Make targets**: Run `make help` to see all available commands
|
||||
- **Team support**: Ask in team channels or create GitHub issues
|
||||
|
||||
## Make Commands Reference
|
||||
|
||||
```bash
|
||||
make install-test # Install test dependencies
|
||||
make test # Run all tests
|
||||
make test-unit # Run unit tests only
|
||||
make test-integration # Run integration tests only
|
||||
make test-fast # Run all except slow tests
|
||||
make test-coverage # Run with coverage report
|
||||
make ci # Full CI pipeline (format, lint, test)
|
||||
```
|
||||
|
||||
Use this guide as your daily reference for testing. The structure is designed to give you fast feedback during development while maintaining comprehensive test coverage.
|
||||
@@ -26,10 +26,8 @@ nav:
|
||||
- CSV Import: guides/csv-import.md
|
||||
- Marketplace Integration: guides/marketplace-integration.md
|
||||
- Testing:
|
||||
- Overview: testing/index.md
|
||||
- Naming Conventions: testing/test-naming-conventions.md
|
||||
- Running Tests: testing/running-tests.md
|
||||
- Test Data: testing/test-data.md
|
||||
- Testing Guide: testing/testing-guide.md
|
||||
- Test Maintenance: testing/test-maintenance.md
|
||||
- Development:
|
||||
- Architecture: development/architecture.md
|
||||
- Database Schema: development/database-schema.md
|
||||
|
||||
@@ -1,171 +0,0 @@
|
||||
# Models Restructure Plan
|
||||
|
||||
## New Structure
|
||||
|
||||
```
|
||||
models/
|
||||
├── database/
|
||||
│ ├── __init__.py # Import all models for easy access
|
||||
│ ├── base.py # Base model class and mixins
|
||||
│ ├── user.py # User model
|
||||
│ ├── shop.py # Shop, ShopProduct models
|
||||
│ ├── product.py # Product model
|
||||
│ ├── stock.py # Stock model
|
||||
│ └── marketplace.py # MarketplaceImportJob model
|
||||
└── api/
|
||||
├── __init__.py # Common imports
|
||||
├── base.py # Base response models
|
||||
├── auth.py # User auth models (register, login, response)
|
||||
├── shop.py # Shop management models
|
||||
├── product.py # Product CRUD models
|
||||
├── stock.py # Stock operation models
|
||||
├── marketplace.py # Marketplace import models
|
||||
└── stats.py # Statistics response models
|
||||
```
|
||||
|
||||
## File Contents Breakdown
|
||||
|
||||
### Database Models
|
||||
|
||||
**models/database/base.py:**
|
||||
```python
|
||||
from datetime import datetime
|
||||
from sqlalchemy import Column, DateTime
|
||||
from app.core.database import Base
|
||||
|
||||
class TimestampMixin:
|
||||
"""Mixin to add created_at and updated_at timestamps to models"""
|
||||
created_at = Column(DateTime, default=datetime.utcnow, nullable=False)
|
||||
updated_at = Column(
|
||||
DateTime, default=datetime.utcnow, onupdate=datetime.utcnow, nullable=False
|
||||
)
|
||||
```
|
||||
|
||||
**models/database/user.py:**
|
||||
- `User` model (lines 12-37 from your current file)
|
||||
|
||||
**models/database/shop.py:**
|
||||
- `Shop` model (lines 40-75)
|
||||
- `ShopProduct` model (lines 155-199)
|
||||
|
||||
**models/database/product.py:**
|
||||
- `Product` model (lines 78-152)
|
||||
|
||||
**models/database/stock.py:**
|
||||
- `Stock` model (lines 202-232)
|
||||
|
||||
**models/database/marketplace.py:**
|
||||
- `MarketplaceImportJob` model (lines 235-284)
|
||||
|
||||
### API Models
|
||||
|
||||
**models/api/base.py:**
|
||||
```python
|
||||
from typing import List, TypeVar, Generic
|
||||
from pydantic import BaseModel
|
||||
|
||||
T = TypeVar('T')
|
||||
|
||||
class ListResponse(BaseModel, Generic[T]):
|
||||
"""Generic list response model"""
|
||||
items: List[T]
|
||||
total: int
|
||||
skip: int
|
||||
limit: int
|
||||
|
||||
class StatusResponse(BaseModel):
|
||||
"""Generic status response"""
|
||||
success: bool
|
||||
message: str
|
||||
```
|
||||
|
||||
**models/api/auth.py:**
|
||||
- `UserRegister` (lines 12-34)
|
||||
- `UserLogin` (lines 37-46)
|
||||
- `UserResponse` (lines 49-61)
|
||||
- `LoginResponse` (lines 64-69)
|
||||
|
||||
**models/api/shop.py:**
|
||||
- `ShopCreate` (lines 72-103)
|
||||
- `ShopUpdate` (lines 106-122)
|
||||
- `ShopResponse` (lines 125-145)
|
||||
- `ShopListResponse` (lines 148-153)
|
||||
- `ShopProductCreate` (lines 247-270)
|
||||
- `ShopProductResponse` (lines 273-293)
|
||||
|
||||
**models/api/product.py:**
|
||||
- `ProductBase` (lines 156-193)
|
||||
- `ProductCreate` (lines 196-206)
|
||||
- `ProductUpdate` (lines 209-211)
|
||||
- `ProductResponse` (lines 214-221)
|
||||
- `ProductListResponse` (lines 408-413)
|
||||
- `ProductDetailResponse` (lines 416-420)
|
||||
|
||||
**models/api/stock.py:**
|
||||
- `StockBase` (lines 296-300)
|
||||
- `StockCreate` (lines 303-305)
|
||||
- `StockAdd` (lines 308-310)
|
||||
- `StockUpdate` (lines 313-315)
|
||||
- `StockResponse` (lines 318-327)
|
||||
- `StockLocationResponse` (lines 330-333)
|
||||
- `StockSummaryResponse` (lines 336-342)
|
||||
|
||||
**models/api/marketplace.py:**
|
||||
- `MarketplaceImportRequest` (lines 345-381)
|
||||
- `MarketplaceImportJobResponse` (lines 384-399)
|
||||
|
||||
**models/api/stats.py:**
|
||||
- `StatsResponse` (lines 423-431)
|
||||
- `MarketplaceStatsResponse` (lines 434-439)
|
||||
|
||||
## Migration Steps
|
||||
|
||||
### Step 1: Create the new structure
|
||||
1. Create the new directories
|
||||
2. Create `__init__.py` files
|
||||
3. Move the base classes first
|
||||
|
||||
### Step 2: Database models migration
|
||||
1. Extract models one by one, starting with `User`
|
||||
2. Update imports in each file
|
||||
3. Test database connections after each model
|
||||
|
||||
### Step 3: API models migration
|
||||
1. Extract API models by domain
|
||||
2. Update imports in route files
|
||||
3. Test API endpoints after each model group
|
||||
|
||||
### Step 4: Update imports across the application
|
||||
- Update all route files to use new import paths
|
||||
- Update service files
|
||||
- Update test files
|
||||
|
||||
## Benefits of This Structure
|
||||
|
||||
1. **Domain separation**: Related models are grouped together
|
||||
2. **Easier maintenance**: Smaller files are easier to navigate and modify
|
||||
3. **Reduced conflicts**: Multiple developers can work on different domains
|
||||
4. **Better testability**: Can test model groups independently
|
||||
5. **Clear dependencies**: Import relationships become more explicit
|
||||
|
||||
## Import Examples After Restructure
|
||||
|
||||
```python
|
||||
# In route files
|
||||
from models.database.user import User, Product, Stock
|
||||
from models.api.auth import UserLogin, UserResponse
|
||||
from models.api.product import ProductCreate, ProductListResponse
|
||||
|
||||
# Or specific imports
|
||||
from models.database.product import Product
|
||||
from models.api.product import ProductCreate, ProductResponse
|
||||
```
|
||||
|
||||
## Consideration for Relationships
|
||||
|
||||
When splitting database models, be careful with SQLAlchemy relationships:
|
||||
- Keep related models in the same file if they have tight coupling
|
||||
- Use string references for relationships across files: `relationship("User")` instead of `relationship(User)`
|
||||
- Consider lazy imports in `__init__.py` files to avoid circular imports
|
||||
|
||||
This restructure will make your codebase much more maintainable as it grows!
|
||||
@@ -1,161 +0,0 @@
|
||||
# restructure_models.ps1 - Final working script using temp files
|
||||
|
||||
Write-Host "📄 Starting models restructure..." -ForegroundColor Cyan
|
||||
|
||||
# Create new directory structure for models
|
||||
Write-Host "📁 Creating models directory structure..." -ForegroundColor Yellow
|
||||
$modelDirectories = @(
|
||||
"models\database",
|
||||
"models\api"
|
||||
)
|
||||
|
||||
foreach ($dir in $modelDirectories) {
|
||||
if (!(Test-Path $dir)) {
|
||||
New-Item -Path $dir -ItemType Directory -Force | Out-Null
|
||||
Write-Host " Created: $dir" -ForegroundColor Green
|
||||
} else {
|
||||
Write-Host " Exists: $dir" -ForegroundColor Gray
|
||||
}
|
||||
}
|
||||
|
||||
# Backup original model files
|
||||
Write-Host "💾 Backing up original model files..." -ForegroundColor Yellow
|
||||
if (!(Test-Path "models\backup")) {
|
||||
New-Item -Path "models\backup" -ItemType Directory -Force | Out-Null
|
||||
}
|
||||
|
||||
$originalFiles = @("models\database_models.py", "models\api_models.py")
|
||||
foreach ($file in $originalFiles) {
|
||||
if (Test-Path $file) {
|
||||
Copy-Item $file "models\backup\" -Force
|
||||
Write-Host " Backed up: $(Split-Path $file -Leaf)" -ForegroundColor Gray
|
||||
}
|
||||
}
|
||||
|
||||
Write-Host "📄 Creating database model files..." -ForegroundColor Yellow
|
||||
|
||||
# Function to create files using temp file approach
|
||||
function New-PythonFile {
|
||||
param(
|
||||
[string]$Path,
|
||||
[string]$TempContent
|
||||
)
|
||||
|
||||
$tempFile = [System.IO.Path]::GetTempFileName()
|
||||
$TempContent | Out-File $tempFile -Encoding UTF8
|
||||
Move-Item $tempFile $Path -Force
|
||||
}
|
||||
|
||||
# Create models/database/__init__.py
|
||||
Write-Host " Creating models\database\__init__.py..." -ForegroundColor Gray
|
||||
$initContent = @"
|
||||
# models/database/__init__.py
|
||||
from .user import User
|
||||
from .product import Product
|
||||
from .shop import Shop, ShopProduct
|
||||
from .stock import Stock
|
||||
from .marketplace import MarketplaceImportJob
|
||||
|
||||
__all__ = [
|
||||
"User",
|
||||
"Product",
|
||||
"Shop", "ShopProduct",
|
||||
"Stock",
|
||||
"MarketplaceImportJob",
|
||||
]
|
||||
"@
|
||||
New-PythonFile -Path "models\database\__init__.py" -TempContent $initContent
|
||||
|
||||
# Create models/database/base.py
|
||||
Write-Host " Creating models\database\base.py..." -ForegroundColor Gray
|
||||
$baseContent = 'LS0gLS0tQkVHSU4gUFlUSE9OIENPREUtLS0tLQojIG1vZGVscy9kYXRhYmFzZS9iYXNlLnB5CmZyb20gZGF0ZXRpbWUgaW1wb3J0IGRhdGV0aW1lCmZyb20gc3FsYWxjaGVteSBpbXBvcnQgQ29sdW1uLCBEYXRlVGltZQpmcm9tIGFwcC5jb3JlLmRhdGFiYXNlIGltcG9ydCBCYXNlCgoKY2xhc3MgVGltZXN0YW1wTWl4aW46CiAgICAiIiJNaXhpbiB0byBhZGQgY3JlYXRlZF9hdCBhbmQgdXBkYXRlZF9hdCB0aW1lc3RhbXBzIHRvIG1vZGVscyIiIgogICAgY3JlYXRlZF9hdCA9IENvbHVtbihEYXRlVGltZSwgZGVmYXVsdD1kYXRldGltZS51dGNub3csIG51bGxhYmxlPUZhbHNlKQogICAgdXBkYXRlZF9hdCA9IENvbHVtbigKICAgICAgICBEYXRlVGltZSwgZGVmYXVsdD1kYXRldGltZS51dGNub3csIG9udXBkYXRlPWRhdGV0aW1lLnV0Y25vdywgbnVsbGFibGU9RmFsc2UKICAgICkKLS0tLS1FTkQgUFlUSE9OIENPREUtLS0tLQo='
|
||||
$decodedBase = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($baseContent))
|
||||
$decodedBase = $decodedBase.Replace('-----BEGIN PYTHON CODE-----', '').Replace('-----END PYTHON CODE-----', '').Trim()
|
||||
$decodedBase | Out-File "models\database\base.py" -Encoding UTF8
|
||||
|
||||
# Create models/database/user.py
|
||||
Write-Host " Creating models\database\user.py..." -ForegroundColor Gray
|
||||
$userContent = 'LS0tLS1CRUdJTiBQWVRIT04gQ09ERS0tLS0tCiMgbW9kZWxzL2RhdGFiYXNlL3VzZXIucHkKZnJvbSBkYXRldGltZSBpbXBvcnQgZGF0ZXRpbWUKZnJvbSBzcWxhbGNoZW15IGltcG9ydCBCb29sZWFuLCBDb2x1bW4sIERhdGVUaW1lLCBJbnRlZ2VyLCBTdHJpbmcKZnJvbSBzcWxhbGNoZW15Lm9ybSBpbXBvcnQgcmVsYXRpb25zaGlwCmZyb20gYXBwLmNvcmUuZGF0YWJhc2UgaW1wb3J0IEJhc2UKCgpjbGFzcyBVc2VyKEJhc2UpOgogICAgX190YWJsZW5hbWVfXyA9ICJ1c2VycyIKCiAgICBpZCA9IENvbHVtbihJbnRlZ2VyLCBwcmltYXJ5X2tleT1UcnVlLCBpbmRleD1UcnVlKQogICAgZW1haWwgPSBDb2x1bW4oU3RyaW5nLCB1bmlxdWU9VHJ1ZSwgaW5kZXg9VHJ1ZSwgbnVsbGFibGU9RmFsc2UpCiAgICB1c2VybmFtZSA9IENvbHVtbihTdHJpbmcsIHVuaXF1ZT1UcnVlLCBpbmRleD1UcnVlLCBudWxsYWJsZT1GYWxzZSkKICAgIGhhc2hlZF9wYXNzd29yZCA9IENvbHVtbihTdHJpbmcsIG51bGxhYmxlPUZhbHNlKQogICAgcm9sZSA9IENvbHVtbihTdHJpbmcsIG51bGxhYmxlPUZhbHNlLCBkZWZhdWx0PSJ1c2VyIikgICMgdXNlciwgYWRtaW4sIHNob3Bfb3duZXIKICAgIGlzX2FjdGl2ZSA9IENvbHVtbihCb29sZWFuLCBkZWZhdWx0PVRydWUsIG51bGxhYmxlPUZhbHNlKQogICAgbGFzdF9sb2dpbiA9IENvbHVtbihEYXRlVGltZSwgbnVsbGFibGU9VHJ1ZSkKICAgIGNyZWF0ZWRfYXQgPSBDb2x1bW4oRGF0ZVRpbWUsIGRlZmF1bHQ9ZGF0ZXRpbWUudXRjbm93LCBudWxsYWJsZT1GYWxzZSkKICAgIHVwZGF0ZWRfYXQgPSBDb2x1bW4oCiAgICAgICAgRGF0ZVRpbWUsIGRlZmF1bHQ9ZGF0ZXRpbWUudXRjbm93LCBvbnVwZGF0ZT1kYXRldGltZS51dGNub3csIG51bGxhYmxlPUZhbHNlCiAgICApCgogICAgIyBSZWxhdGlvbnNoaXBzCiAgICBtYXJrZXRwbGFjZV9pbXBvcnRfam9icyA9IHJlbGF0aW9uc2hpcCgKICAgICAgICAiTWFya2V0cGxhY2VJbXBvcnRKb2IiLCBiYWNrX3BvcHVsYXRlcz0idXNlciIKICAgICkKICAgIG93bmVkX3Nob3BzID0gcmVsYXRpb25zaGlwKCJTaG9wIiwgYmFja19wb3B1bGF0ZXM9Im93bmVyIikKCiAgICBkZWYgX19yZXByX18oc2VsZik6CiAgICAgICAgcmV0dXJuIGYiPFVzZXIodXNlcm5hbWU9J3tzZWxmLnVzZXJuYW1lfScsIGVtYWlsPSd7c2VsZi5lbWFpbH0nLCByb2xlPSd7c2VsZi5yb2xlfScpPiIKLS0tLS1FTkQgUFlUSE9OIENPREUtLS0tLQo='
|
||||
$decodedUser = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($userContent))
|
||||
$decodedUser = $decodedUser.Replace('-----BEGIN PYTHON CODE-----', '').Replace('-----END PYTHON CODE-----', '').Trim()
|
||||
$decodedUser | Out-File "models\database\user.py" -Encoding UTF8
|
||||
|
||||
# Create models/api/__init__.py
|
||||
Write-Host " Creating models\api\__init__.py..." -ForegroundColor Gray
|
||||
$apiInitContent = @"
|
||||
# models/api/__init__.py
|
||||
# Import statements will be added as you create the individual API model files
|
||||
# Example:
|
||||
# from .auth import UserRegister, UserLogin, UserResponse, LoginResponse
|
||||
|
||||
__all__ = [
|
||||
# Add your API model imports here as you create them
|
||||
]
|
||||
"@
|
||||
New-PythonFile -Path "models\api\__init__.py" -TempContent $apiInitContent
|
||||
|
||||
# Create models/api/base.py
|
||||
Write-Host " Creating models\api\base.py..." -ForegroundColor Gray
|
||||
$apiBaseContent = 'LS0tLS1CRUdJTiBQWVRIT04gQ09ERS0tLS0tCiMgbW9kZWxzL2FwaS9iYXNlLnB5CmZyb20gdHlwaW5nIGltcG9ydCBMaXN0LCBUeXBlVmFyLCBHZW5lcmljCmZyb20gcHlkYW50aWMgaW1wb3J0IEJhc2VNb2RlbAoKVCA9IFR5cGVWYXIoJ1QnKQoKY2xhc3MgTGlzdFJlc3BvbnNlKEJhc2VNb2RlbCwgR2VuZXJpY1tUXSk6CiAgICAiIiJHZW5lcmljIGxpc3QgcmVzcG9uc2UgbW9kZWwiIiIKICAgIGl0ZW1zOiBMaXN0W1RdCiAgICB0b3RhbDogaW50CiAgICBza2lwOiBpbnQKICAgIGxpbWl0OiBpbnQKCmNsYXNzIFN0YXR1c1Jlc3BvbnNlKEJhc2VNb2RlbCk6CiAgICAiIiJHZW5lcmljIHN0YXR1cyByZXNwb25zZSIiIgogICAgc3VjY2VzczogYm9vbAogICAgbWVzc2FnZTogc3RyCi0tLS0tRU5EIFBZVEhPTiBDT0RFLS0tLS0K'
|
||||
$decodedApi = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($apiBaseContent))
|
||||
$decodedApi = $decodedApi.Replace('-----BEGIN PYTHON CODE-----', '').Replace('-----END PYTHON CODE-----', '').Trim()
|
||||
$decodedApi | Out-File "models\api\base.py" -Encoding UTF8
|
||||
|
||||
Write-Host "✅ Basic model structure created!" -ForegroundColor Green
|
||||
Write-Host ""
|
||||
Write-Host "🔍 Testing file creation..." -ForegroundColor Yellow
|
||||
|
||||
# Check if files were created
|
||||
$createdFiles = @(
|
||||
"models\database\__init__.py",
|
||||
"models\database\base.py",
|
||||
"models\database\user.py",
|
||||
"models\api\__init__.py",
|
||||
"models\api\base.py"
|
||||
)
|
||||
|
||||
foreach ($file in $createdFiles) {
|
||||
if (Test-Path $file) {
|
||||
$size = (Get-Item $file).Length
|
||||
Write-Host "✅ $file ($size bytes)" -ForegroundColor Green
|
||||
} else {
|
||||
Write-Host "❌ $file (missing)" -ForegroundColor Red
|
||||
}
|
||||
}
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "🧪 Testing Python imports..." -ForegroundColor Yellow
|
||||
try {
|
||||
$pythonTest = 'import sys; sys.path.append("."); from models.database.user import User; print("SUCCESS: Database models import working")'
|
||||
$testResult = python -c $pythonTest 2>&1
|
||||
if ($LASTEXITCODE -eq 0) {
|
||||
Write-Host "✅ Python import test passed!" -ForegroundColor Green
|
||||
} else {
|
||||
Write-Host "⚠️ Python import test failed (may need dependencies)" -ForegroundColor Yellow
|
||||
}
|
||||
} catch {
|
||||
Write-Host "⚠️ Python not available for testing" -ForegroundColor Yellow
|
||||
}
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "📋 Next steps:" -ForegroundColor Cyan
|
||||
Write-Host "1. Complete the remaining database models by copying from your original files:" -ForegroundColor White
|
||||
Write-Host " - models\database\product.py" -ForegroundColor Gray
|
||||
Write-Host " - models\database\shop.py" -ForegroundColor Gray
|
||||
Write-Host " - models\database\stock.py" -ForegroundColor Gray
|
||||
Write-Host " - models\database\marketplace.py" -ForegroundColor Gray
|
||||
Write-Host ""
|
||||
Write-Host "2. Extract API model classes from your api_models.py to separate files:" -ForegroundColor White
|
||||
Write-Host " - models\api\auth.py" -ForegroundColor Gray
|
||||
Write-Host " - models\api\product.py" -ForegroundColor Gray
|
||||
Write-Host " - models\api\shop.py" -ForegroundColor Gray
|
||||
Write-Host " - models\api\stock.py" -ForegroundColor Gray
|
||||
Write-Host " - models\api\marketplace.py" -ForegroundColor Gray
|
||||
Write-Host " - models\api\stats.py" -ForegroundColor Gray
|
||||
Write-Host ""
|
||||
Write-Host "3. Update imports throughout your application:" -ForegroundColor White
|
||||
Write-Host " Old: from models.database.user import User" -ForegroundColor Gray
|
||||
Write-Host " New: from models.database.user import User" -ForegroundColor Gray
|
||||
Write-Host ""
|
||||
Write-Host "✨ Foundation completed successfully!" -ForegroundColor Green
|
||||
Write-Host "📚 Original files backed up in models\backup\" -ForegroundColor Green
|
||||
@@ -1,159 +0,0 @@
|
||||
Yes, absolutely! As your application grows, splitting the models into separate files by domain/entity is a common and recommended practice. Here's how you could restructure your models:
|
||||
|
||||
## Recommended Structure:
|
||||
|
||||
```
|
||||
models/
|
||||
├── database/
|
||||
│ ├── __init__.py # Import all models for easy access
|
||||
│ ├── base.py # Base model class and common mixins
|
||||
│ ├── user.py # User, UserProfile models
|
||||
│ ├── auth.py # Authentication-related models
|
||||
│ ├── product.py # Product, ProductVariant models
|
||||
│ ├── stock.py # Stock, StockMovement models
|
||||
│ ├── shop.py # Shop, ShopLocation models
|
||||
│ ├── marketplace.py # Marketplace integration models
|
||||
│ └── admin.py # Admin-specific models
|
||||
└── api/
|
||||
├── __init__.py # Common imports
|
||||
├── base.py # Base Pydantic models
|
||||
├── auth.py # Login, Token, User response models
|
||||
├── product.py # Product request/response models
|
||||
├── stock.py # Stock operation models
|
||||
├── shop.py # Shop management models
|
||||
├── marketplace.py # Marketplace import models
|
||||
├── admin.py # Admin operation models
|
||||
└── stats.py # Statistics response models
|
||||
```
|
||||
|
||||
## Benefits of this approach:
|
||||
|
||||
1. **Maintainability**: Easier to find and modify specific model types
|
||||
2. **Team collaboration**: Reduces merge conflicts when multiple developers work on different features
|
||||
3. **Import clarity**: Clear imports like `from models.database.product import Product`
|
||||
4. **Logical grouping**: Related models stay together
|
||||
5. **Reduced file size**: Each file focuses on a specific domain
|
||||
|
||||
## Example implementation:
|
||||
|
||||
**models/database/__init__.py:**
|
||||
```python
|
||||
from .user import User, UserProfile
|
||||
from .product import Product, ProductVariant
|
||||
from .stock import Stock, StockMovement
|
||||
from .shop import Shop, ShopLocation
|
||||
# ... other imports
|
||||
|
||||
__all__ = [
|
||||
"User", "UserProfile",
|
||||
"Product", "ProductVariant",
|
||||
"Stock", "StockMovement",
|
||||
"Shop", "ShopLocation",
|
||||
# ... other exports
|
||||
]
|
||||
```
|
||||
|
||||
**models/api/__init__.py:**
|
||||
```python
|
||||
from .auth import LoginRequest, TokenResponse, UserResponse
|
||||
from .product import ProductCreate, ProductResponse, ProductUpdate
|
||||
# ... other imports
|
||||
|
||||
__all__ = [
|
||||
"LoginRequest", "TokenResponse", "UserResponse",
|
||||
"ProductCreate", "ProductResponse", "ProductUpdate",
|
||||
# ... other exports
|
||||
]
|
||||
```
|
||||
|
||||
This way, you can still import everything easily in your route files:
|
||||
```python
|
||||
from models.database import Product, Stock
|
||||
from models.api import ProductCreate, ProductResponse
|
||||
```
|
||||
|
||||
The split makes most sense when you have 5+ models per domain or when individual model files exceed ~200-300 lines.
|
||||
|
||||
Looking at your current `api_models.py` and `database_models.py`, they're definitely getting quite large and would benefit from being split up! Here's how I'd recommend restructuring them:Based on your current models, here are the key considerations for splitting them:
|
||||
|
||||
## Immediate Benefits You'll See:
|
||||
|
||||
1. **File Size**: Your current `api_models.py` is 439 lines and `database_models.py` is 284 lines - splitting will make them much more manageable
|
||||
|
||||
2. **Domain Clarity**: You have clear domains (auth, shop, product, stock, marketplace) that naturally separate
|
||||
|
||||
3. **Import Organization**: Instead of importing everything from two massive files, you'll have clear, purposeful imports
|
||||
|
||||
## Potential Challenges to Watch For:
|
||||
|
||||
1. **Circular Imports**: With SQLAlchemy relationships, you might need to use string references like `relationship("User")` instead of the class directly
|
||||
|
||||
2. **Shared Validators**: You have some common validation logic that might need to be moved to a base/utils module
|
||||
|
||||
3. **Import Updates**: You'll need to update all your route files, but this is a one-time cost
|
||||
|
||||
## Recommended Migration Order:
|
||||
|
||||
1. **Start with API models** - they're easier since they don't have complex relationships
|
||||
2. **Begin with auth models** - they're the most independent
|
||||
3. **Then do database models** - be careful with relationships
|
||||
4. **Update imports gradually** - do it route by route to avoid breaking everything at once
|
||||
|
||||
Would you like me to create the actual split files for any specific domain (like auth or products) to get you started?
|
||||
|
||||
I've created a comprehensive PowerShell script that automatically generates the models restructure outlined in our discussion. This script:
|
||||
|
||||
## What the Script Creates:
|
||||
|
||||
**Database Models Structure:**
|
||||
- `models/database/` with separate files for each domain
|
||||
- `base.py` with TimestampMixin for common functionality
|
||||
- `user.py`, `product.py`, `shop.py`, `stock.py`, `marketplace.py`
|
||||
- Proper `__init__.py` with all imports configured
|
||||
|
||||
**API Models Structure:**
|
||||
- `models/api/` directory structure
|
||||
- `base.py` with generic response models
|
||||
- `__init__.py` with comprehensive imports ready
|
||||
|
||||
**Key Features:**
|
||||
- **Automatic backup** of your original files
|
||||
- **Complete database models** extracted and properly organized
|
||||
- **Import structure** ready with `__init__.py` files
|
||||
- **Clear next steps** printed after completion
|
||||
|
||||
## Usage:
|
||||
|
||||
```powershell
|
||||
# Navigate to your project root
|
||||
cd your-project-directory
|
||||
# Run the script
|
||||
.\restructure_models.ps1
|
||||
```
|
||||
|
||||
## What You'll Still Need to Do Manually:
|
||||
|
||||
The script creates the database models completely, but you'll need to extract the API model classes from your existing `api_models.py` into the new structure:
|
||||
|
||||
1. **Auth models** → `models/api/auth.py`
|
||||
2. **Product models** → `models/api/product.py`
|
||||
3. **Shop models** → `models/api/shop.py`
|
||||
4. **Stock models** → `models/api/stock.py`
|
||||
5. **Marketplace models** → `models/api/marketplace.py`
|
||||
6. **Stats models** → `models/api/stats.py`
|
||||
|
||||
## Import Updates Needed:
|
||||
|
||||
After running the script, update imports throughout your codebase:
|
||||
|
||||
```python
|
||||
# Old imports
|
||||
from models.database.user import User, Product, Shop
|
||||
from models.api import UserRegister, ProductResponse
|
||||
|
||||
# New imports
|
||||
from models.database.user import User, Product, Shop
|
||||
from models.api import UserRegister, ProductResponse
|
||||
```
|
||||
|
||||
The script will complete the database models restructure automatically and provide you with a clear roadmap for finishing the API models migration.
|
||||
@@ -1,523 +0,0 @@
|
||||
# FastAPI Service Layer Architecture
|
||||
|
||||
This document describes the service layer architecture implemented in our FastAPI application, providing clear separation of concerns between HTTP handling (routers) and business logic (services).
|
||||
|
||||
## Table of Contents
|
||||
|
||||
- [Overview](#overview)
|
||||
- [Architecture Pattern](#architecture-pattern)
|
||||
- [Service Layer Components](#service-layer-components)
|
||||
- [Benefits](#benefits)
|
||||
- [Implementation Guide](#implementation-guide)
|
||||
- [Testing Strategy](#testing-strategy)
|
||||
- [Usage Examples](#usage-examples)
|
||||
- [Best Practices](#best-practices)
|
||||
- [Migration Guide](#migration-guide)
|
||||
|
||||
## Overview
|
||||
|
||||
Our FastAPI application follows a **Service Layer Architecture** pattern that separates HTTP concerns from business logic. This architecture provides better maintainability, testability, and code reusability.
|
||||
|
||||
### Before vs After
|
||||
|
||||
**Before (Router handling everything):**
|
||||
```python
|
||||
@router.post("/products")
|
||||
def create_product(product: ProductCreate, db: Session = Depends(get_db)):
|
||||
# Validation logic
|
||||
if product.gtin:
|
||||
normalized_gtin = normalize_gtin(product.gtin)
|
||||
if not normalized_gtin:
|
||||
raise HTTPException(status_code=400, detail="Invalid GTIN")
|
||||
|
||||
# Business logic
|
||||
db_product = Product(**product.model_dump())
|
||||
db.add(db_product)
|
||||
db.commit()
|
||||
|
||||
# Return response
|
||||
return db_product
|
||||
```
|
||||
|
||||
**After (Service layer separation):**
|
||||
```python
|
||||
# Router (HTTP concerns only)
|
||||
@router.post("/products")
|
||||
def create_product(product: ProductCreate, db: Session = Depends(get_db)):
|
||||
try:
|
||||
return product_service.create_product(db, product)
|
||||
except ValueError as e:
|
||||
raise HTTPException(status_code=400, detail=str(e))
|
||||
|
||||
# Service (Business logic)
|
||||
class ProductService:
|
||||
def create_product(self, db: Session, product_data: ProductCreate) -> Product:
|
||||
# All validation and business logic here
|
||||
# Returns domain objects, raises domain exceptions
|
||||
```
|
||||
|
||||
## Architecture Pattern
|
||||
|
||||
```
|
||||
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
|
||||
│ HTTP Client │ │ FastAPI App │ │ Database │
|
||||
└─────────────────┘ └─────────────────┘ └─────────────────┘
|
||||
│ │ │
|
||||
│ HTTP Request │ │
|
||||
├──────────────────────▶ │ │
|
||||
│ │ │
|
||||
│ ┌─────────────────┐ │
|
||||
│ │ Router │ │
|
||||
│ │ (HTTP Layer) │ │
|
||||
│ └─────────────────┘ │
|
||||
│ │ │
|
||||
│ │ Delegates to │
|
||||
│ ▼ │
|
||||
│ ┌─────────────────┐ │
|
||||
│ │ Service │ │
|
||||
│ │ (Business Layer)│ │
|
||||
│ └─────────────────┘ │
|
||||
│ │ │
|
||||
│ │ Database Operations │
|
||||
│ ├──────────────────────▶│
|
||||
│ │ │
|
||||
│ HTTP Response │ │
|
||||
◀──────────────────────── │ │
|
||||
```
|
||||
|
||||
## Service Layer Components
|
||||
|
||||
### 1. ProductService
|
||||
|
||||
**File:** `product_service.py`
|
||||
|
||||
**Responsibilities:**
|
||||
- Product CRUD operations
|
||||
- GTIN validation and normalization
|
||||
- Price processing
|
||||
- Stock information retrieval
|
||||
- CSV export generation
|
||||
|
||||
**Key Methods:**
|
||||
```python
|
||||
class ProductService:
|
||||
def create_product(self, db: Session, product_data: ProductCreate) -> Product
|
||||
def get_product_by_id(self, db: Session, product_id: str) -> Optional[Product]
|
||||
def get_products_with_filters(self, db: Session, **filters) -> tuple[List[Product], int]
|
||||
def update_product(self, db: Session, product_id: str, product_update: ProductUpdate) -> Product
|
||||
def delete_product(self, db: Session, product_id: str) -> bool
|
||||
def get_stock_info(self, db: Session, gtin: str) -> Optional[StockSummaryResponse]
|
||||
def generate_csv_export(self, db: Session, **filters) -> Generator[str, None, None]
|
||||
```
|
||||
|
||||
### 2. StockService
|
||||
|
||||
**File:** `stock_service.py`
|
||||
|
||||
**Responsibilities:**
|
||||
- Stock quantity management
|
||||
- GTIN normalization
|
||||
- Stock location tracking
|
||||
- Stock operations (set, add, remove)
|
||||
|
||||
**Key Methods:**
|
||||
```python
|
||||
class StockService:
|
||||
def set_stock(self, db: Session, stock_data: StockCreate) -> Stock
|
||||
def add_stock(self, db: Session, stock_data: StockAdd) -> Stock
|
||||
def remove_stock(self, db: Session, stock_data: StockAdd) -> Stock
|
||||
def get_stock_by_gtin(self, db: Session, gtin: str) -> StockSummaryResponse
|
||||
def get_total_stock(self, db: Session, gtin: str) -> dict
|
||||
def get_all_stock(self, db: Session, **filters) -> List[Stock]
|
||||
def normalize_gtin(self, gtin_value) -> Optional[str]
|
||||
```
|
||||
|
||||
### 3. MarketplaceService
|
||||
|
||||
**File:** `marketplace_service.py`
|
||||
|
||||
**Responsibilities:**
|
||||
- Marketplace import job management
|
||||
- Shop access validation
|
||||
- User permission checking
|
||||
- Job lifecycle management
|
||||
- Import statistics
|
||||
|
||||
**Key Methods:**
|
||||
```python
|
||||
class MarketplaceService:
|
||||
def validate_shop_access(self, db: Session, shop_code: str, user: User) -> Shop
|
||||
def create_import_job(self, db: Session, request: MarketplaceImportRequest, user: User) -> MarketplaceImportJob
|
||||
def get_import_job_by_id(self, db: Session, job_id: int, user: User) -> MarketplaceImportJob
|
||||
def get_import_jobs(self, db: Session, user: User, **filters) -> List[MarketplaceImportJob]
|
||||
def update_job_status(self, db: Session, job_id: int, status: str, **kwargs) -> MarketplaceImportJob
|
||||
def cancel_import_job(self, db: Session, job_id: int, user: User) -> MarketplaceImportJob
|
||||
def delete_import_job(self, db: Session, job_id: int, user: User) -> bool
|
||||
```
|
||||
|
||||
## Benefits
|
||||
|
||||
### 1. **Separation of Concerns**
|
||||
- **Routers**: Handle HTTP requests/responses, authentication, validation of HTTP-specific concerns
|
||||
- **Services**: Handle business logic, data validation, domain rules
|
||||
- **Models**: Define data structures and database relationships
|
||||
|
||||
### 2. **Better Testability**
|
||||
- **Unit Testing**: Test business logic independently of HTTP layer
|
||||
- **Integration Testing**: Test HTTP endpoints separately from business logic
|
||||
- **Mocking**: Easy to mock services for router testing
|
||||
|
||||
### 3. **Code Reusability**
|
||||
- Services can be used from multiple routers
|
||||
- Background tasks can use services directly
|
||||
- CLI commands can leverage the same business logic
|
||||
|
||||
### 4. **Maintainability**
|
||||
- Business logic changes only need to be made in one place
|
||||
- Clear boundaries between different layers
|
||||
- Easier to understand and modify code
|
||||
|
||||
### 5. **Error Handling**
|
||||
- Domain-specific exceptions in services
|
||||
- Consistent HTTP error mapping in routers
|
||||
- Better error messages and logging
|
||||
|
||||
## Implementation Guide
|
||||
|
||||
### Step 1: Create Service Class
|
||||
|
||||
```python
|
||||
# product_service.py
|
||||
class ProductService:
|
||||
def __init__(self):
|
||||
self.gtin_processor = GTINProcessor()
|
||||
self.price_processor = PriceProcessor()
|
||||
|
||||
def create_product(self, db: Session, product_data: ProductCreate) -> Product:
|
||||
# Business logic here
|
||||
# Raise ValueError for business logic errors
|
||||
# Return domain objects
|
||||
pass
|
||||
|
||||
# Create singleton instance
|
||||
product_service = ProductService()
|
||||
```
|
||||
|
||||
### Step 2: Refactor Router
|
||||
|
||||
```python
|
||||
# product.py
|
||||
from product_service import product_service
|
||||
|
||||
@router.post("/products", response_model=ProductResponse)
|
||||
def create_product(
|
||||
product: ProductCreate,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user)
|
||||
):
|
||||
try:
|
||||
result = product_service.create_product(db, product)
|
||||
return result
|
||||
except ValueError as e:
|
||||
raise HTTPException(status_code=400, detail=str(e))
|
||||
except Exception as e:
|
||||
logger.error(f"Error creating product: {str(e)}")
|
||||
raise HTTPException(status_code=500, detail="Internal server error")
|
||||
```
|
||||
|
||||
### Step 3: Error Handling Pattern
|
||||
|
||||
**Service Layer Exceptions:**
|
||||
- `ValueError`: Business logic validation errors → HTTP 400
|
||||
- `PermissionError`: Access control errors → HTTP 403
|
||||
- Custom exceptions for specific domain errors
|
||||
|
||||
**Router Error Translation:**
|
||||
```python
|
||||
try:
|
||||
result = service.method(db, data)
|
||||
return result
|
||||
except ValueError as e:
|
||||
raise HTTPException(status_code=400, detail=str(e))
|
||||
except PermissionError as e:
|
||||
raise HTTPException(status_code=403, detail=str(e))
|
||||
except Exception as e:
|
||||
logger.error(f"Unexpected error: {str(e)}")
|
||||
raise HTTPException(status_code=500, detail="Internal server error")
|
||||
```
|
||||
|
||||
## Testing Strategy
|
||||
|
||||
### Service Layer Tests
|
||||
|
||||
```python
|
||||
# test_product_service.py
|
||||
class TestProductService:
|
||||
def setup_method(self):
|
||||
self.service = ProductService()
|
||||
|
||||
def test_create_product_success(self, db):
|
||||
product_data = ProductCreate(product_id="TEST001", title="Test Product")
|
||||
result = self.service.create_product(db, product_data)
|
||||
|
||||
assert result.product_id == "TEST001"
|
||||
assert result.title == "Test Product"
|
||||
|
||||
def test_create_product_invalid_gtin(self, db):
|
||||
product_data = ProductCreate(product_id="TEST001", gtin="invalid")
|
||||
|
||||
with pytest.raises(ValueError, match="Invalid GTIN format"):
|
||||
self.service.create_product(db, product_data)
|
||||
```
|
||||
|
||||
### Router Tests
|
||||
|
||||
```python
|
||||
# test_products_api.py
|
||||
def test_create_product_endpoint(self, client, auth_headers):
|
||||
product_data = {"product_id": "TEST001", "title": "Test Product"}
|
||||
response = client.post("/api/v1/product", headers=auth_headers, json=product_data)
|
||||
|
||||
assert response.status_code == 200
|
||||
assert response.json()["product_id"] == "TEST001"
|
||||
|
||||
def test_create_product_validation_error(self, client, auth_headers):
|
||||
product_data = {"product_id": "TEST001", "gtin": "invalid"}
|
||||
response = client.post("/api/v1/product", headers=auth_headers, json=product_data)
|
||||
|
||||
assert response.status_code == 400
|
||||
assert "Invalid GTIN format" in response.json()["detail"]
|
||||
```
|
||||
|
||||
### Test Organization
|
||||
|
||||
```
|
||||
tests/
|
||||
├── test_services.py # Service layer unit tests
|
||||
├── test_product_service.py # ProductService specific tests
|
||||
├── test_stock_service.py # StockService specific tests
|
||||
├── test_marketplace_service.py # MarketplaceService specific tests
|
||||
├── test_products.py # Product API integration tests
|
||||
├── test_stock.py # Stock API integration tests
|
||||
└── test_marketplace.py # Marketplace API integration tests
|
||||
```
|
||||
|
||||
## Usage Examples
|
||||
|
||||
### Using Services in Background Tasks
|
||||
|
||||
```python
|
||||
# background_tasks.py
|
||||
from product_service import product_service
|
||||
from stock_service import stock_service
|
||||
|
||||
async def process_marketplace_import(job_id: int, csv_data: str):
|
||||
try:
|
||||
for row in csv_data:
|
||||
# Create product using service
|
||||
product = product_service.create_product(db, product_data)
|
||||
|
||||
# Update stock using service
|
||||
if product.gtin:
|
||||
stock_service.set_stock(db, stock_data)
|
||||
|
||||
except Exception as e:
|
||||
marketplace_service.update_job_status(db, job_id, "failed", error_message=str(e))
|
||||
```
|
||||
|
||||
### Using Services in CLI Commands
|
||||
|
||||
```python
|
||||
# cli_commands.py
|
||||
from product_service import product_service
|
||||
|
||||
def bulk_update_prices(csv_file: str):
|
||||
"""CLI command to bulk update product prices"""
|
||||
for product_id, new_price in read_csv(csv_file):
|
||||
try:
|
||||
product_service.update_product(
|
||||
db, product_id,
|
||||
ProductUpdate(price=new_price)
|
||||
)
|
||||
print(f"Updated {product_id}")
|
||||
except ValueError as e:
|
||||
print(f"Error updating {product_id}: {e}")
|
||||
```
|
||||
|
||||
### Service Composition
|
||||
|
||||
```python
|
||||
# order_service.py (hypothetical)
|
||||
from product_service import product_service
|
||||
from stock_service import stock_service
|
||||
|
||||
class OrderService:
|
||||
def create_order(self, db: Session, order_data: OrderCreate) -> Order:
|
||||
for item in order_data.items:
|
||||
# Check product exists
|
||||
product = product_service.get_product_by_id(db, item.product_id)
|
||||
if not product:
|
||||
raise ValueError(f"Product {item.product_id} not found")
|
||||
|
||||
# Check stock availability
|
||||
stock = stock_service.get_total_stock(db, product.gtin)
|
||||
if stock["total_quantity"] < item.quantity:
|
||||
raise ValueError(f"Insufficient stock for {item.product_id}")
|
||||
|
||||
# Reserve stock
|
||||
stock_service.remove_stock(db, StockAdd(
|
||||
gtin=product.gtin,
|
||||
location="RESERVED",
|
||||
quantity=item.quantity
|
||||
))
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
### 1. Service Design Principles
|
||||
|
||||
- **Single Responsibility**: Each service handles one domain
|
||||
- **Dependency Injection**: Services should not create their own dependencies
|
||||
- **Pure Functions**: Methods should be predictable and side-effect free when possible
|
||||
- **Domain Exceptions**: Use meaningful exception types
|
||||
|
||||
### 2. Error Handling
|
||||
|
||||
```python
|
||||
# Good: Specific domain exceptions
|
||||
class ProductService:
|
||||
def create_product(self, db: Session, product_data: ProductCreate) -> Product:
|
||||
if self.product_exists(db, product_data.product_id):
|
||||
raise ValueError(f"Product {product_data.product_id} already exists")
|
||||
|
||||
if product_data.gtin and not self.is_valid_gtin(product_data.gtin):
|
||||
raise ValueError("Invalid GTIN format")
|
||||
|
||||
# Business logic...
|
||||
|
||||
# Bad: Generic exceptions
|
||||
def create_product(product_data):
|
||||
if product_exists(product_data.product_id):
|
||||
raise Exception("Error occurred") # Too generic
|
||||
```
|
||||
|
||||
### 3. Testing Guidelines
|
||||
|
||||
- **Test business logic in service tests**
|
||||
- **Test HTTP behavior in router tests**
|
||||
- **Use fixtures for common test data**
|
||||
- **Mock external dependencies**
|
||||
|
||||
### 4. Documentation
|
||||
|
||||
- Document service methods with docstrings
|
||||
- Include parameter types and return types
|
||||
- Document exceptions that can be raised
|
||||
- Provide usage examples
|
||||
|
||||
```python
|
||||
def create_product(self, db: Session, product_data: ProductCreate) -> Product:
|
||||
"""Create a new product with validation.
|
||||
|
||||
Args:
|
||||
db: Database session
|
||||
product_data: Product creation data
|
||||
|
||||
Returns:
|
||||
Created Product instance
|
||||
|
||||
Raises:
|
||||
ValueError: If product_id already exists or GTIN is invalid
|
||||
|
||||
Example:
|
||||
>>> service = ProductService()
|
||||
>>> product = service.create_product(db, ProductCreate(
|
||||
... product_id="ABC123",
|
||||
... title="Test Product",
|
||||
... gtin="1234567890123"
|
||||
... ))
|
||||
"""
|
||||
```
|
||||
|
||||
## Migration Guide
|
||||
|
||||
### Migrating Existing Routers
|
||||
|
||||
1. **Identify Business Logic**: Look for code that validates data, processes business rules, or manipulates domain objects
|
||||
|
||||
2. **Create Service Class**: Extract business logic into service methods
|
||||
|
||||
3. **Update Router**: Replace business logic with service calls and add error handling
|
||||
|
||||
4. **Add Tests**: Create comprehensive tests for the service layer
|
||||
|
||||
5. **Verify Compatibility**: Ensure API behavior remains the same
|
||||
|
||||
### Example Migration
|
||||
|
||||
**Before:**
|
||||
```python
|
||||
@router.post("/products")
|
||||
def create_product(product: ProductCreate, db: Session = Depends(get_db)):
|
||||
# Check if product exists
|
||||
existing = db.query(Product).filter(Product.product_id == product.product_id).first()
|
||||
if existing:
|
||||
raise HTTPException(status_code=400, detail="Product already exists")
|
||||
|
||||
# Validate GTIN
|
||||
if product.gtin:
|
||||
normalized_gtin = normalize_gtin(product.gtin)
|
||||
if not normalized_gtin:
|
||||
raise HTTPException(status_code=400, detail="Invalid GTIN")
|
||||
product.gtin = normalized_gtin
|
||||
|
||||
# Create product
|
||||
db_product = Product(**product.model_dump())
|
||||
db.add(db_product)
|
||||
db.commit()
|
||||
db.refresh(db_product)
|
||||
|
||||
return db_product
|
||||
```
|
||||
|
||||
**After:**
|
||||
```python
|
||||
# Service
|
||||
class ProductService:
|
||||
def create_product(self, db: Session, product_data: ProductCreate) -> Product:
|
||||
# Check if product exists
|
||||
if self.product_exists(db, product_data.product_id):
|
||||
raise ValueError("Product already exists")
|
||||
|
||||
# Validate GTIN
|
||||
if product_data.gtin:
|
||||
normalized_gtin = self.gtin_processor.normalize(product_data.gtin)
|
||||
if not normalized_gtin:
|
||||
raise ValueError("Invalid GTIN")
|
||||
product_data.gtin = normalized_gtin
|
||||
|
||||
# Create product
|
||||
db_product = Product(**product_data.model_dump())
|
||||
db.add(db_product)
|
||||
db.commit()
|
||||
db.refresh(db_product)
|
||||
|
||||
return db_product
|
||||
|
||||
# Router
|
||||
@router.post("/products")
|
||||
def create_product(product: ProductCreate, db: Session = Depends(get_db)):
|
||||
try:
|
||||
return product_service.create_product(db, product)
|
||||
except ValueError as e:
|
||||
raise HTTPException(status_code=400, detail=str(e))
|
||||
except Exception as e:
|
||||
logger.error(f"Error creating product: {str(e)}")
|
||||
raise HTTPException(status_code=500, detail="Internal server error")
|
||||
```
|
||||
|
||||
## Conclusion
|
||||
|
||||
The service layer architecture provides a robust foundation for scalable FastAPI applications. By separating HTTP concerns from business logic, we achieve better maintainability, testability, and code reuse. This pattern scales well as applications grow and makes it easier to add new features while maintaining existing functionality.
|
||||
|
||||
For questions or contributions to this architecture, please refer to the team documentation or create an issue in the project repository.
|
||||
@@ -1,218 +0,0 @@
|
||||
# Your Specific Test Migration Plan
|
||||
|
||||
## New Directory Structure
|
||||
|
||||
```
|
||||
tests/
|
||||
├── conftest.py # Your current global fixtures (keep most of it)
|
||||
├── pytest.ini # New - test configuration
|
||||
├── fixtures/ # Extract fixtures from conftest.py
|
||||
│ ├── __init__.py
|
||||
│ ├── auth_fixtures.py # User, admin fixtures
|
||||
│ ├── product_fixtures.py # Product-related fixtures
|
||||
│ ├── shop_fixtures.py # Shop-related fixtures
|
||||
│ └── database_fixtures.py # DB setup fixtures
|
||||
├── unit/ # Fast, isolated tests
|
||||
│ ├── __init__.py
|
||||
│ ├── conftest.py # Unit-specific fixtures
|
||||
│ ├── models/
|
||||
│ │ ├── __init__.py
|
||||
│ │ └── test_database_models.py # From test_database.py
|
||||
│ ├── utils/
|
||||
│ │ ├── __init__.py
|
||||
│ │ └── test_data_processing.py # From test_utils.py
|
||||
│ └── services/
|
||||
│ ├── __init__.py
|
||||
│ └── test_admin_service.py # Keep as-is, move here
|
||||
├── integration/ # Multi-component tests
|
||||
│ ├── __init__.py
|
||||
│ ├── conftest.py
|
||||
│ ├── api/
|
||||
│ │ ├── __init__.py
|
||||
│ │ ├── conftest.py # API client fixtures
|
||||
│ │ └── v1/
|
||||
│ │ ├── __init__.py
|
||||
│ │ └── test_admin_endpoints.py # From test_admin.py
|
||||
│ └── security/
|
||||
│ ├── __init__.py
|
||||
│ ├── test_authentication.py # Split from test_security.py
|
||||
│ ├── test_authorization.py # Split from test_security.py
|
||||
│ └── test_input_validation.py # Split from test_security.py
|
||||
├── system/ # Full system tests
|
||||
│ ├── __init__.py
|
||||
│ ├── conftest.py
|
||||
│ └── test_error_handling.py # From test_error_handling.py
|
||||
└── test_data/ # Test data files
|
||||
└── csv/
|
||||
└── sample_products.csv
|
||||
```
|
||||
|
||||
## Specific File Mappings
|
||||
|
||||
### 1. Global Fixtures (Keep in main conftest.py)
|
||||
**tests/conftest.py** - Keep these fixtures:
|
||||
- `engine`, `testing_session_local`, `db`
|
||||
- `client`
|
||||
- `cleanup`
|
||||
|
||||
### 2. Extract to Fixture Files
|
||||
|
||||
**tests/fixtures/auth_fixtures.py:**
|
||||
```python
|
||||
# Move these from conftest.py:
|
||||
- auth_manager
|
||||
- test_user
|
||||
- test_admin
|
||||
- auth_headers
|
||||
- admin_headers
|
||||
- other_user
|
||||
```
|
||||
|
||||
**tests/fixtures/product_fixtures.py:**
|
||||
```python
|
||||
# Move these from conftest.py:
|
||||
- test_product
|
||||
- unique_product
|
||||
- multiple_products
|
||||
- product_factory
|
||||
```
|
||||
|
||||
**tests/fixtures/shop_fixtures.py:**
|
||||
```python
|
||||
# Move these from conftest.py:
|
||||
- test_shop
|
||||
- unique_shop
|
||||
- inactive_shop
|
||||
- verified_shop
|
||||
- shop_product
|
||||
- shop_factory
|
||||
```
|
||||
|
||||
### 3. Unit Tests
|
||||
|
||||
**tests/unit/models/test_database_models.py:**
|
||||
Move content from `test_database.py`:
|
||||
- `TestDatabaseModels.test_user_model`
|
||||
- `TestDatabaseModels.test_product_model`
|
||||
- `TestDatabaseModels.test_stock_model`
|
||||
- `TestDatabaseModels.test_shop_model_with_owner`
|
||||
- `TestDatabaseModels.test_database_constraints`
|
||||
|
||||
**tests/unit/utils/test_data_processing.py:**
|
||||
Move content from `test_utils.py`:
|
||||
- `TestGTINProcessor` (entire class)
|
||||
- `TestPriceProcessor` (entire class)
|
||||
|
||||
**tests/unit/services/test_admin_service.py:**
|
||||
Keep `test_admin_service.py` exactly as-is, just move to this location.
|
||||
|
||||
### 4. Integration Tests
|
||||
|
||||
**tests/integration/api/v1/test_admin_endpoints.py:**
|
||||
Move content from `test_admin.py`:
|
||||
- `TestAdminAPI` (entire class) - all your admin endpoint tests
|
||||
|
||||
**tests/integration/security/test_authentication.py:**
|
||||
Move from `test_security.py`:
|
||||
```python
|
||||
class TestAuthentication:
|
||||
def test_protected_endpoint_without_auth(self, client):
|
||||
# From test_security.py
|
||||
|
||||
def test_protected_endpoint_with_invalid_token(self, client):
|
||||
# From test_security.py
|
||||
```
|
||||
|
||||
**tests/integration/security/test_authorization.py:**
|
||||
Move from `test_security.py`:
|
||||
```python
|
||||
class TestAuthorization:
|
||||
def test_admin_endpoint_requires_admin_role(self, client, auth_headers):
|
||||
# From test_security.py
|
||||
```
|
||||
|
||||
**tests/integration/security/test_input_validation.py:**
|
||||
Move from `test_security.py`:
|
||||
```python
|
||||
class TestInputValidation:
|
||||
def test_sql_injection_prevention(self, client, auth_headers):
|
||||
# From test_security.py
|
||||
|
||||
# def test_input_validation(self, client, auth_headers):
|
||||
# # Your commented XSS test
|
||||
```
|
||||
|
||||
### 5. System Tests
|
||||
|
||||
**tests/system/test_error_handling.py:**
|
||||
Move `test_error_handling.py` as-is to this location.
|
||||
|
||||
## Migration Steps
|
||||
|
||||
### Step 1: Create Structure
|
||||
```bash
|
||||
mkdir -p tests/{fixtures,unit/{models,utils,services},integration/{api/v1,security},system,test_data}
|
||||
touch tests/{fixtures,unit,integration,system}/__init__.py
|
||||
touch tests/unit/{models,utils,services}/__init__.py
|
||||
touch tests/integration/{api,security}/__init__.py
|
||||
touch tests/integration/api/v1/__init__.py
|
||||
```
|
||||
|
||||
### Step 2: Create pytest.ini
|
||||
```ini
|
||||
[tool:pytest]
|
||||
testpaths = tests
|
||||
python_files = test_*.py
|
||||
addopts = -v --tb=short
|
||||
markers =
|
||||
unit: Unit tests - fast, isolated
|
||||
integration: Integration tests - multiple components
|
||||
system: System tests - full application
|
||||
slow: Slow running tests
|
||||
```
|
||||
|
||||
### Step 3: Extract Fixtures
|
||||
Create fixture files and move relevant fixtures from your current `conftest.py`.
|
||||
|
||||
### Step 4: Move Test Files
|
||||
Move each test file to its new location and update imports.
|
||||
|
||||
### Step 5: Update Imports
|
||||
After moving files, update imports like:
|
||||
```python
|
||||
# Old import in test files
|
||||
# No explicit imports needed since fixtures were in conftest.py
|
||||
|
||||
# New imports in test files
|
||||
from tests.fixtures.auth_fixtures import test_user, auth_headers
|
||||
from tests.fixtures.product_fixtures import test_product
|
||||
```
|
||||
|
||||
## Running Tests by Category
|
||||
|
||||
```bash
|
||||
# Fast unit tests during development
|
||||
pytest tests/unit -m unit
|
||||
|
||||
# Integration tests before commit
|
||||
pytest tests/integration -m integration
|
||||
|
||||
# Full test suite
|
||||
pytest
|
||||
|
||||
# Specific domain tests
|
||||
pytest tests/unit/services/ tests/integration/api/v1/test_admin_endpoints.py
|
||||
|
||||
# Your current debug tests (move to integration/security)
|
||||
pytest tests/integration/security/ -v -s
|
||||
```
|
||||
|
||||
## Benefits for Your Specific Tests
|
||||
|
||||
1. **Your admin tests** get separated into service logic (unit) vs API endpoints (integration)
|
||||
2. **Your security tests** get properly categorized by concern
|
||||
3. **Your database tests** become proper model unit tests
|
||||
4. **Your utility tests** become isolated unit tests
|
||||
5. **Your error handling** becomes system-level testing
|
||||
|
||||
This structure will make your test suite much more maintainable and allow for faster development cycles!
|
||||
@@ -1,262 +0,0 @@
|
||||
# Test Structure Migration Guide
|
||||
|
||||
This document outlines the complete restructuring of the FastAPI application test suite from a single-folder approach to a comprehensive, scalable test organization.
|
||||
|
||||
## Overview
|
||||
|
||||
The test suite has been reorganized from:
|
||||
```
|
||||
tests/
|
||||
├── conftest.py
|
||||
├── test_admin.py
|
||||
├── test_admin_service.py
|
||||
├── test_database.py
|
||||
├── test_error_handling.py
|
||||
├── test_pagination.py
|
||||
├── test_performance.py
|
||||
├── test_security.py
|
||||
├── test_utils.py
|
||||
└── pytest.ini
|
||||
```
|
||||
|
||||
To a structured, domain-organized approach that scales with application growth and provides better development workflow support.
|
||||
|
||||
## New Structure
|
||||
|
||||
```
|
||||
tests/
|
||||
├── conftest.py # Core test configuration and database fixtures
|
||||
├── pytest.ini # Enhanced pytest configuration
|
||||
├── fixtures/ # Shared test fixtures organized by domain
|
||||
│ ├── __init__.py
|
||||
│ ├── auth_fixtures.py # Authentication: users, tokens, headers
|
||||
│ ├── product_fixtures.py # Products: test products, factories
|
||||
│ ├── shop_fixtures.py # Shops: shops, stock, shop-products
|
||||
│ └── marketplace_fixtures.py # Marketplace: import jobs
|
||||
├── unit/ # Fast, isolated component tests
|
||||
│ ├── __init__.py
|
||||
│ ├── conftest.py # Unit test specific fixtures
|
||||
│ ├── models/
|
||||
│ │ ├── __init__.py
|
||||
│ │ └── test_database_models.py # Database model tests
|
||||
│ ├── utils/
|
||||
│ │ ├── __init__.py
|
||||
│ │ └── test_data_processing.py # Utility function tests
|
||||
│ └── services/
|
||||
│ ├── __init__.py
|
||||
│ └── test_admin_service.py # Business logic tests
|
||||
├── integration/ # Multi-component interaction tests
|
||||
│ ├── __init__.py
|
||||
│ ├── conftest.py # Integration test fixtures
|
||||
│ ├── api/
|
||||
│ │ ├── __init__.py
|
||||
│ │ ├── conftest.py # API client fixtures
|
||||
│ │ └── v1/
|
||||
│ │ ├── __init__.py
|
||||
│ │ ├── test_admin_endpoints.py # Admin API endpoint tests
|
||||
│ │ └── test_pagination.py # Pagination functionality tests
|
||||
│ └── security/
|
||||
│ ├── __init__.py
|
||||
│ ├── test_authentication.py # Authentication mechanism tests
|
||||
│ ├── test_authorization.py # Permission and role tests
|
||||
│ └── test_input_validation.py # Input security and validation tests
|
||||
├── performance/ # Performance and load tests
|
||||
│ ├── __init__.py
|
||||
│ ├── conftest.py # Performance test fixtures
|
||||
│ └── test_api_performance.py # API performance benchmarks
|
||||
├── system/ # End-to-end system behavior tests
|
||||
│ ├── __init__.py
|
||||
│ ├── conftest.py # System test fixtures
|
||||
│ └── test_error_handling.py # Application error handling tests
|
||||
└── test_data/ # Test data files
|
||||
└── csv/
|
||||
└── sample_products.csv # Sample CSV data for testing
|
||||
```
|
||||
|
||||
## Migration Changes Made
|
||||
|
||||
### 1. Fixture Organization
|
||||
**Problem Solved:** The original `conftest.py` contained 370+ lines mixing different concerns.
|
||||
|
||||
**Solution:** Extracted domain-specific fixtures into separate modules:
|
||||
- **auth_fixtures.py**: User authentication, tokens, and headers
|
||||
- **product_fixtures.py**: Product creation, factories, and bulk data
|
||||
- **shop_fixtures.py**: Shop management, stock, and shop-product relationships
|
||||
- **marketplace_fixtures.py**: Import job fixtures and helpers
|
||||
|
||||
### 2. Test Categorization
|
||||
**Problem Solved:** All tests ran together, making development cycles slow.
|
||||
|
||||
**Solution:** Organized tests by execution speed and scope:
|
||||
- **Unit Tests**: Fast (< 100ms), isolated, no external dependencies
|
||||
- **Integration Tests**: Medium speed, multiple components, database required
|
||||
- **Performance Tests**: Slow, large datasets, timing-sensitive
|
||||
- **System Tests**: Full application behavior, error scenarios
|
||||
|
||||
### 3. Enhanced pytest Configuration
|
||||
**Previous:** Basic configuration with limited markers.
|
||||
|
||||
**Current:** Comprehensive configuration including:
|
||||
```ini
|
||||
[tool:pytest]
|
||||
addopts =
|
||||
-v --tb=short --strict-markers --strict-config
|
||||
--color=yes --durations=10 --showlocals -ra
|
||||
--cov=app --cov=models --cov=utils --cov=middleware
|
||||
--cov-report=term-missing --cov-report=html:htmlcov
|
||||
--cov-fail-under=80
|
||||
|
||||
markers =
|
||||
unit: Unit tests - fast, isolated components
|
||||
integration: Integration tests - multiple components
|
||||
system: System tests - full application behavior
|
||||
performance: Performance and load tests
|
||||
slow: Slow running tests
|
||||
# Domain-specific markers
|
||||
auth: Authentication and authorization tests
|
||||
products: Product management functionality
|
||||
admin: Admin functionality and permissions
|
||||
# ... additional markers
|
||||
```
|
||||
|
||||
## Usage Examples
|
||||
|
||||
### Development Workflow
|
||||
```bash
|
||||
# Fast feedback during development (unit tests only)
|
||||
pytest -m unit
|
||||
|
||||
# Before committing (unit + integration)
|
||||
pytest -m "unit or integration"
|
||||
|
||||
# Full test suite (CI/CD)
|
||||
pytest
|
||||
|
||||
# Skip slow tests during development
|
||||
pytest -m "not slow"
|
||||
```
|
||||
|
||||
### Domain-Specific Testing
|
||||
```bash
|
||||
# Test specific functionality
|
||||
pytest -m auth # Authentication tests
|
||||
pytest -m products # Product-related tests
|
||||
pytest -m admin # Admin functionality
|
||||
|
||||
# Test specific layers
|
||||
pytest tests/unit/ # All unit tests
|
||||
pytest tests/integration/api/ # API integration tests
|
||||
pytest tests/performance/ # Performance tests only
|
||||
```
|
||||
|
||||
### Coverage and Reporting
|
||||
```bash
|
||||
# Generate coverage report
|
||||
pytest --cov-report=html
|
||||
|
||||
# Find slowest tests
|
||||
pytest --durations=0
|
||||
|
||||
# Detailed failure information
|
||||
pytest -vvv --tb=long
|
||||
```
|
||||
|
||||
## Key Improvements
|
||||
|
||||
### 1. Scalability
|
||||
- **Modular fixture organization** allows teams to work on different domains without conflicts
|
||||
- **Clear separation of concerns** makes it easy to add new test categories
|
||||
- **Factory patterns** provide flexible test data generation
|
||||
|
||||
### 2. Development Speed
|
||||
- **Fast unit tests** provide immediate feedback (typically runs in < 10 seconds)
|
||||
- **Selective test execution** allows developers to run only relevant tests
|
||||
- **Parallel execution support** ready (uncomment `addopts = -n auto` in pytest.ini)
|
||||
|
||||
### 3. Maintainability
|
||||
- **Domain organization** makes it easy to locate and update tests
|
||||
- **Consistent fixture patterns** reduce duplication
|
||||
- **Clear naming conventions** improve code readability
|
||||
|
||||
### 4. CI/CD Pipeline Optimization
|
||||
- **Staged test execution**: Run fast tests first, fail fast on basic issues
|
||||
- **Performance monitoring**: Track test execution times and performance regressions
|
||||
- **Coverage tracking**: Maintain code coverage standards
|
||||
|
||||
## Migration Process
|
||||
|
||||
### Automated Setup
|
||||
1. Run the migration script:
|
||||
```bash
|
||||
bash migrate_tests.sh
|
||||
```
|
||||
|
||||
### Manual Steps
|
||||
1. **Copy fixture files** to `tests/fixtures/`
|
||||
2. **Update main conftest.py** with the new version
|
||||
3. **Move test files** to their new locations:
|
||||
- `test_database.py` → `tests/unit/models/test_database_models.py`
|
||||
- `test_utils.py` → `tests/unit/utils/test_data_processing.py`
|
||||
- `test_admin_service.py` → `tests/unit/services/`
|
||||
- `test_admin.py` → `tests/integration/api/v1/test_admin_endpoints.py`
|
||||
- Split `test_security.py` into `tests/integration/security/` files
|
||||
- `test_pagination.py` → `tests/integration/api/v1/`
|
||||
- `test_performance.py` → `tests/performance/test_api_performance.py`
|
||||
- `test_error_handling.py` → `tests/system/`
|
||||
|
||||
4. **Update imports** in moved files
|
||||
5. **Add pytest markers** to test classes
|
||||
6. **Test incrementally**:
|
||||
```bash
|
||||
pytest tests/unit -v
|
||||
pytest tests/integration -v
|
||||
pytest -m unit
|
||||
```
|
||||
|
||||
## Benefits Realized
|
||||
|
||||
### For Developers
|
||||
- **Faster feedback loops**: Unit tests complete in seconds
|
||||
- **Focused testing**: Run only tests relevant to current work
|
||||
- **Better debugging**: Clear test categorization helps identify issues quickly
|
||||
|
||||
### For Teams
|
||||
- **Reduced merge conflicts**: Domain separation allows parallel development
|
||||
- **Clear ownership**: Teams can own test domains matching their code ownership
|
||||
- **Consistent patterns**: Standardized fixture and test organization
|
||||
|
||||
### For CI/CD
|
||||
- **Optimized pipeline**: Fast tests run first, expensive tests only when needed
|
||||
- **Performance monitoring**: Track performance regressions over time
|
||||
- **Coverage enforcement**: Maintain quality standards automatically
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Import Issues
|
||||
If you encounter import errors after migration:
|
||||
1. Ensure all `__init__.py` files are created
|
||||
2. Check that `pytest_plugins` in main `conftest.py` points to correct fixture modules
|
||||
3. Verify fixture dependencies (e.g., `test_shop` depends on `test_user`)
|
||||
|
||||
### Fixture Not Found
|
||||
If pytest can't find fixtures:
|
||||
1. Check that fixture modules are listed in `pytest_plugins`
|
||||
2. Ensure fixture dependencies are imported properly
|
||||
3. Verify fixture scope (session vs function) matches usage
|
||||
|
||||
### Performance Issues
|
||||
If tests are running slowly:
|
||||
1. Check that unit tests don't have database dependencies
|
||||
2. Consider using `pytest-xdist` for parallel execution
|
||||
3. Review slow tests with `pytest --durations=10`
|
||||
|
||||
## Future Enhancements
|
||||
|
||||
This structure supports future additions:
|
||||
- **E2E tests**: Add `tests/e2e/` for complete user journey testing
|
||||
- **Contract tests**: Add API contract testing with tools like Pact
|
||||
- **Load tests**: Expand performance testing with tools like Locust
|
||||
- **Visual tests**: Add screenshot testing for frontend components
|
||||
- **Mutation tests**: Add mutation testing to verify test quality
|
||||
|
||||
The restructured test suite provides a solid foundation for maintaining high code quality as the application scales.
|
||||
@@ -46,7 +46,8 @@ clean:
|
||||
|
||||
# Install test dependencies
|
||||
install-test-deps:
|
||||
pip install -r tests/requirements_test.txtvalidate_csv_headers(valid_df) == True
|
||||
pip install -r tests/requirements_test.txt
|
||||
validate_csv_headers(valid_df) == True
|
||||
|
||||
# Invalid headers (missing required fields)
|
||||
invalid_df = pd.DataFrame({
|
||||
|
||||
@@ -1,352 +0,0 @@
|
||||
# Tests Folder Restructure Plan
|
||||
|
||||
## Current vs Recommended Structure
|
||||
|
||||
### Before (Single Folder)
|
||||
```
|
||||
tests/
|
||||
├── test_auth.py
|
||||
├── test_products.py
|
||||
├── test_stock.py
|
||||
├── test_shops.py
|
||||
├── test_marketplace.py
|
||||
├── test_admin.py
|
||||
├── test_stats.py
|
||||
├── test_database.py
|
||||
├── test_utils.py
|
||||
├── conftest.py
|
||||
└── ...more files
|
||||
```
|
||||
|
||||
### After (Organized Structure)
|
||||
```
|
||||
tests/
|
||||
├── conftest.py # Global test configuration and fixtures
|
||||
├── pytest.ini # Pytest configuration
|
||||
├── __init__.py
|
||||
├── fixtures/ # Shared test fixtures
|
||||
│ ├── __init__.py
|
||||
│ ├── auth_fixtures.py # Auth-related fixtures
|
||||
│ ├── product_fixtures.py # Product fixtures
|
||||
│ ├── shop_fixtures.py # Shop fixtures
|
||||
│ └── database_fixtures.py # Database setup fixtures
|
||||
├── unit/ # Unit tests (isolated, fast)
|
||||
│ ├── __init__.py
|
||||
│ ├── conftest.py # Unit test specific fixtures
|
||||
│ ├── models/ # Test database and API models
|
||||
│ │ ├── __init__.py
|
||||
│ │ ├── test_user_model.py
|
||||
│ │ ├── test_product_model.py
|
||||
│ │ ├── test_shop_model.py
|
||||
│ │ └── test_stock_model.py
|
||||
│ ├── utils/ # Test utility functions
|
||||
│ │ ├── __init__.py
|
||||
│ │ ├── test_data_processing.py
|
||||
│ │ ├── test_csv_processor.py
|
||||
│ │ └── test_database_utils.py
|
||||
│ ├── services/ # Test business logic
|
||||
│ │ ├── __init__.py
|
||||
│ │ ├── test_auth_service.py
|
||||
│ │ ├── test_product_service.py
|
||||
│ │ └── test_marketplace_service.py
|
||||
│ └── middleware/ # Test middleware components
|
||||
│ ├── __init__.py
|
||||
│ ├── test_auth_middleware.py
|
||||
│ ├── test_rate_limiter.py
|
||||
│ └── test_error_handler.py
|
||||
├── integration/ # Integration tests (multiple components)
|
||||
│ ├── __init__.py
|
||||
│ ├── conftest.py # Integration test fixtures
|
||||
│ ├── api/ # API endpoint tests
|
||||
│ │ ├── __init__.py
|
||||
│ │ ├── conftest.py # API test fixtures (test client, etc.)
|
||||
│ │ ├── v1/
|
||||
│ │ │ ├── __init__.py
|
||||
│ │ │ ├── test_auth_endpoints.py
|
||||
│ │ │ ├── test_product_endpoints.py
|
||||
│ │ │ ├── test_shop_endpoints.py
|
||||
│ │ │ ├── test_stock_endpoints.py
|
||||
│ │ │ ├── test_marketplace_endpoints.py
|
||||
│ │ │ ├── test_admin_endpoints.py
|
||||
│ │ │ └── test_stats_endpoints.py
|
||||
│ │ └── test_api_main.py # Test API router setup
|
||||
│ ├── database/ # Database integration tests
|
||||
│ │ ├── __init__.py
|
||||
│ │ ├── test_crud_operations.py
|
||||
│ │ ├── test_relationships.py
|
||||
│ │ └── test_migrations.py
|
||||
│ └── workflows/ # End-to-end workflow tests
|
||||
│ ├── __init__.py
|
||||
│ ├── test_product_import_workflow.py
|
||||
│ ├── test_shop_setup_workflow.py
|
||||
│ └── test_stock_management_workflow.py
|
||||
├── e2e/ # End-to-end tests (full application)
|
||||
│ ├── __init__.py
|
||||
│ ├── conftest.py
|
||||
│ ├── test_user_registration_flow.py
|
||||
│ ├── test_marketplace_import_flow.py
|
||||
│ └── test_complete_shop_setup.py
|
||||
├── performance/ # Performance and load tests
|
||||
│ ├── __init__.py
|
||||
│ ├── test_api_performance.py
|
||||
│ ├── test_database_performance.py
|
||||
│ └── test_import_performance.py
|
||||
└── test_data/ # Test data files
|
||||
├── csv/
|
||||
│ ├── valid_products.csv
|
||||
│ ├── invalid_products.csv
|
||||
│ └── large_dataset.csv
|
||||
├── json/
|
||||
│ ├── sample_product.json
|
||||
│ └── marketplace_response.json
|
||||
└── fixtures/
|
||||
├── test_users.json
|
||||
└── test_products.json
|
||||
```
|
||||
|
||||
## File Organization Principles
|
||||
|
||||
### 1. Test Types Separation
|
||||
- **Unit Tests**: Fast, isolated tests for individual functions/classes
|
||||
- **Integration Tests**: Tests that involve multiple components working together
|
||||
- **E2E Tests**: Full application flow tests
|
||||
- **Performance Tests**: Load and performance testing
|
||||
|
||||
### 2. Fixture Organization
|
||||
```python
|
||||
# tests/fixtures/auth_fixtures.py
|
||||
import pytest
|
||||
from models.database.user import User
|
||||
from utils.auth import create_access_token
|
||||
|
||||
@pytest.fixture
|
||||
def test_user_data():
|
||||
return {
|
||||
"email": "test@example.com",
|
||||
"username": "testuser",
|
||||
"password": "testpass123"
|
||||
}
|
||||
|
||||
@pytest.fixture
|
||||
def authenticated_user(db_session, test_user_data):
|
||||
user = User(**test_user_data)
|
||||
db_session.add(user)
|
||||
db_session.commit()
|
||||
return user
|
||||
|
||||
@pytest.fixture
|
||||
def auth_headers(authenticated_user):
|
||||
token = create_access_token(data={"sub": authenticated_user.username})
|
||||
return {"Authorization": f"Bearer {token}"}
|
||||
```
|
||||
|
||||
### 3. Conftest.py Structure
|
||||
```python
|
||||
# tests/conftest.py (Global fixtures)
|
||||
import pytest
|
||||
from fastapi.testclient import TestClient
|
||||
from sqlalchemy import create_engine
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
|
||||
from main import app
|
||||
from app.core.database import get_db, Base
|
||||
|
||||
# Database fixtures
|
||||
@pytest.fixture(scope="session")
|
||||
def test_engine():
|
||||
engine = create_engine("sqlite:///test.db")
|
||||
Base.metadata.create_all(bind=engine)
|
||||
yield engine
|
||||
Base.metadata.drop_all(bind=engine)
|
||||
|
||||
@pytest.fixture
|
||||
def db_session(test_engine):
|
||||
TestingSessionLocal = sessionmaker(bind=test_engine)
|
||||
session = TestingSessionLocal()
|
||||
yield session
|
||||
session.close()
|
||||
|
||||
@pytest.fixture
|
||||
def client(db_session):
|
||||
def override_get_db():
|
||||
yield db_session
|
||||
|
||||
app.dependency_overrides[get_db] = override_get_db
|
||||
yield TestClient(app)
|
||||
app.dependency_overrides.clear()
|
||||
```
|
||||
|
||||
```python
|
||||
# tests/integration/api/conftest.py (API-specific fixtures)
|
||||
import pytest
|
||||
|
||||
@pytest.fixture
|
||||
def api_client(client):
|
||||
"""Pre-configured API client for integration tests"""
|
||||
return client
|
||||
|
||||
@pytest.fixture
|
||||
def admin_client(client, admin_auth_headers):
|
||||
"""API client with admin authentication"""
|
||||
client.headers.update(admin_auth_headers)
|
||||
return client
|
||||
```
|
||||
|
||||
## Test Naming Conventions
|
||||
|
||||
### File Naming
|
||||
- `test_*.py` for all test files
|
||||
- Mirror your app structure: `test_product_endpoints.py` for `api/v1/products.py`
|
||||
- Use descriptive names: `test_marketplace_import_workflow.py`
|
||||
|
||||
### Test Function Naming
|
||||
```python
|
||||
# Good test naming patterns
|
||||
def test_create_product_with_valid_data_returns_201():
|
||||
pass
|
||||
|
||||
def test_create_product_without_title_returns_422():
|
||||
pass
|
||||
|
||||
def test_get_product_by_id_returns_product_data():
|
||||
pass
|
||||
|
||||
def test_get_nonexistent_product_returns_404():
|
||||
pass
|
||||
|
||||
def test_update_product_stock_updates_quantity():
|
||||
pass
|
||||
|
||||
def test_marketplace_import_with_invalid_csv_fails_gracefully():
|
||||
pass
|
||||
```
|
||||
|
||||
## Running Tests by Category
|
||||
|
||||
### Pytest Configuration (pytest.ini)
|
||||
```ini
|
||||
[tool:pytest]
|
||||
testpaths = tests
|
||||
python_files = test_*.py
|
||||
python_classes = Test*
|
||||
python_functions = test_*
|
||||
addopts =
|
||||
-v
|
||||
--tb=short
|
||||
--strict-markers
|
||||
markers =
|
||||
unit: Unit tests
|
||||
integration: Integration tests
|
||||
e2e: End-to-end tests
|
||||
performance: Performance tests
|
||||
slow: Slow running tests
|
||||
database: Tests that require database
|
||||
external: Tests that require external services
|
||||
```
|
||||
|
||||
### Running Specific Test Categories
|
||||
```bash
|
||||
# Run only unit tests (fast)
|
||||
pytest tests/unit -m unit
|
||||
|
||||
# Run only integration tests
|
||||
pytest tests/integration -m integration
|
||||
|
||||
# Run all tests except slow ones
|
||||
pytest -m "not slow"
|
||||
|
||||
# Run only database tests
|
||||
pytest -m database
|
||||
|
||||
# Run tests for specific domain
|
||||
pytest tests/unit/models/ tests/integration/api/v1/test_product_endpoints.py
|
||||
|
||||
# Run with coverage
|
||||
pytest --cov=app --cov-report=html
|
||||
```
|
||||
|
||||
## Benefits of This Structure
|
||||
|
||||
1. **Faster Development**: Developers can run relevant test subsets
|
||||
2. **Clear Separation**: Easy to understand what each test covers
|
||||
3. **Parallel Execution**: Can run different test types in parallel
|
||||
4. **Maintenance**: Easier to maintain and update tests
|
||||
5. **CI/CD Pipeline**: Can have different pipeline stages for different test types
|
||||
|
||||
## Migration Strategy
|
||||
|
||||
### Phase 1: Create Structure
|
||||
1. Create new directory structure
|
||||
2. Move global fixtures to `fixtures/` directory
|
||||
3. Update `conftest.py` files
|
||||
|
||||
### Phase 2: Move Unit Tests
|
||||
1. Start with utility and model tests
|
||||
2. Move to `tests/unit/` with appropriate subdirectories
|
||||
3. Update imports and fixtures
|
||||
|
||||
### Phase 3: Move Integration Tests
|
||||
1. Move API endpoint tests to `tests/integration/api/`
|
||||
2. Create database integration tests
|
||||
3. Add workflow tests
|
||||
|
||||
### Phase 4: Add Missing Coverage
|
||||
1. Add performance tests if needed
|
||||
2. Create E2E tests for critical flows
|
||||
3. Add proper test markers
|
||||
|
||||
## Example Test File Structure
|
||||
|
||||
### Unit Test Example
|
||||
```python
|
||||
# tests/unit/models/test_product_model.py
|
||||
import pytest
|
||||
from models.database import Product
|
||||
|
||||
class TestProductModel:
|
||||
def test_product_creation_with_valid_data(self, db_session):
|
||||
product = Product(
|
||||
product_id="TEST123",
|
||||
title="Test Product",
|
||||
price="99.99"
|
||||
)
|
||||
db_session.add(product)
|
||||
db_session.commit()
|
||||
|
||||
assert product.id is not None
|
||||
assert product.product_id == "TEST123"
|
||||
assert product.title == "Test Product"
|
||||
|
||||
def test_product_gtin_relationship_with_stock(self, db_session, test_product, test_stock):
|
||||
# Test the GTIN-based relationship
|
||||
assert test_product.gtin in [stock.gtin for stock in test_product.stock_entries]
|
||||
```
|
||||
|
||||
### Integration Test Example
|
||||
```python
|
||||
# tests/integration/api/v1/test_product_endpoints.py
|
||||
import pytest
|
||||
|
||||
class TestProductEndpoints:
|
||||
def test_create_product_endpoint(self, client, auth_headers, valid_product_data):
|
||||
response = client.post(
|
||||
"/api/v1/products/",
|
||||
json=valid_product_data,
|
||||
headers=auth_headers
|
||||
)
|
||||
assert response.status_code == 201
|
||||
assert response.json()["product_id"] == valid_product_data["product_id"]
|
||||
|
||||
def test_get_products_with_pagination(self, client, auth_headers, multiple_products):
|
||||
response = client.get(
|
||||
"/api/v1/products/?skip=0&limit=10",
|
||||
headers=auth_headers
|
||||
)
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert "products" in data
|
||||
assert "total" in data
|
||||
assert len(data["products"]) <= 10
|
||||
```
|
||||
|
||||
This structure will scale beautifully as your application grows and makes it much easier for your team to maintain and extend the test suite!
|
||||
@@ -1,569 +0,0 @@
|
||||
# Ecommerce Backend API v2.1
|
||||
|
||||
A robust, production-ready FastAPI backend for ecommerce product catalog and inventory management with JWT authentication and advanced CSV import capabilities.
|
||||
|
||||
## Key Features
|
||||
|
||||
### Security & Authentication
|
||||
- **JWT Authentication**: Secure token-based authentication with configurable expiration (30 minutes default)
|
||||
- **User Management**: Registration, login, role-based access control (Admin/User roles)
|
||||
- **Password Security**: Bcrypt hashing for secure password storage
|
||||
- **Protected Endpoints**: All product management operations require authentication
|
||||
- **Default Admin Account**: Auto-created admin user for immediate system access
|
||||
|
||||
### Architecture Improvements
|
||||
- **Modular Design**: Separated concerns into utility modules, middleware, and models
|
||||
- **Database Optimization**: Added proper indexing strategy and foreign key relationships
|
||||
- **Connection Pooling**: PostgreSQL support with connection pooling for production scalability
|
||||
- **Background Processing**: Asynchronous CSV import with job tracking
|
||||
|
||||
### Performance Optimizations
|
||||
- **Batch Processing**: CSV imports processed in configurable batches
|
||||
- **Database Indexes**: Strategic indexing for common query patterns
|
||||
- **Streaming Export**: Memory-efficient CSV export for large datasets
|
||||
- **Rate Limiting**: Sliding window rate limiter to prevent API abuse
|
||||
|
||||
### Data Processing
|
||||
- **Robust GTIN Handling**: Centralized GTIN normalization and validation
|
||||
- **Multi-currency Support**: Advanced price parsing with currency extraction
|
||||
- **International Content**: Multi-encoding CSV support for global data
|
||||
|
||||
## Project Structure
|
||||
|
||||
```
|
||||
ecommerce_api/
|
||||
├── main.py # FastAPI application entry point with auth
|
||||
├── models/
|
||||
│ ├── database_models.py # SQLAlchemy ORM models (User, Product, Stock, ImportJob)
|
||||
│ └── api_models.py # Pydantic API models with auth models
|
||||
├── utils/
|
||||
│ ├── data_processing.py # GTIN and price processing utilities
|
||||
│ ├── csv_processor.py # CSV import/export handling
|
||||
│ └── database.py # Database configuration
|
||||
├── middleware/
|
||||
│ ├── auth.py # JWT authentication with bcrypt
|
||||
│ ├── rate_limiter.py # Rate limiting implementation
|
||||
│ ├── error_handler.py # Centralized error handling
|
||||
│ └── logging_middleware.py # Request/response logging
|
||||
├── tests/
|
||||
│ └── test_auth.py # Authentication tests
|
||||
├── requirements.txt # Python dependencies with auth packages
|
||||
└── README.md # This file
|
||||
```
|
||||
|
||||
## Quick Start
|
||||
|
||||
### 1. Installation
|
||||
|
||||
```bash
|
||||
# Clone the repository
|
||||
git clone <repository-url>
|
||||
cd ecommerce-api
|
||||
|
||||
# Set up virtual environment
|
||||
python -m venv venv
|
||||
source venv/bin/activate # On Windows: venv\Scripts\activate
|
||||
|
||||
# Install dependencies
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
### 2. Environment Configuration
|
||||
|
||||
Create a `.env` file in the project root:
|
||||
|
||||
```env
|
||||
# Database
|
||||
DATABASE_URL=postgresql://user:password@localhost:5432/ecommerce_db
|
||||
# For SQLite (development): DATABASE_URL=sqlite:///./ecommerce.db
|
||||
|
||||
# JWT Configuration
|
||||
JWT_SECRET_KEY=your-super-secret-key-change-in-production-immediately
|
||||
JWT_EXPIRE_MINUTES=30
|
||||
|
||||
# Server Configuration
|
||||
API_HOST=0.0.0.0
|
||||
API_PORT=8000
|
||||
DEBUG=False
|
||||
```
|
||||
|
||||
**Important Security Note**: Always change the `JWT_SECRET_KEY` in production!
|
||||
|
||||
### 3. Database Setup
|
||||
|
||||
**For SQLite (Development):**
|
||||
```bash
|
||||
# Run the application - it will create tables automatically
|
||||
python main.py
|
||||
```
|
||||
|
||||
**For PostgreSQL (Production):**
|
||||
```bash
|
||||
# Create PostgreSQL database
|
||||
createdb ecommerce_db
|
||||
|
||||
# Run the application - it will create tables and indexes automatically
|
||||
python main.py
|
||||
```
|
||||
|
||||
### 4. Start the Server
|
||||
|
||||
```bash
|
||||
# Development server
|
||||
uvicorn main:app --reload --host 0.0.0.0 --port 8000
|
||||
|
||||
# Production server
|
||||
uvicorn main:app --host 0.0.0.0 --port 8000 --workers 4
|
||||
```
|
||||
|
||||
The API will be available at `http://localhost:8000`
|
||||
|
||||
### 5. Default Admin Access
|
||||
|
||||
The system automatically creates a default admin user:
|
||||
- **Username**: `admin`
|
||||
- **Password**: `admin123`
|
||||
- **Email**: `admin@example.com`
|
||||
|
||||
**Security Warning**: Change the admin password immediately in production!
|
||||
|
||||
## Authentication Flow
|
||||
|
||||
### 1. Register a New User
|
||||
|
||||
```bash
|
||||
curl -X POST "http://localhost:8000/register" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"email": "user@example.com",
|
||||
"username": "newuser",
|
||||
"password": "securepassword123"
|
||||
}'
|
||||
```
|
||||
|
||||
### 2. Login and Get JWT Token
|
||||
|
||||
```bash
|
||||
curl -X POST "http://localhost:8000/login" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"username": "admin",
|
||||
"password": "admin123"
|
||||
}'
|
||||
```
|
||||
|
||||
Response:
|
||||
```json
|
||||
{
|
||||
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
|
||||
"token_type": "bearer",
|
||||
"expires_in": 1800,
|
||||
"user": {
|
||||
"id": 1,
|
||||
"username": "admin",
|
||||
"email": "admin@example.com",
|
||||
"role": "admin",
|
||||
"is_active": true
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Use Token for Protected Endpoints
|
||||
|
||||
```bash
|
||||
curl -X GET "http://localhost:8000/products" \
|
||||
-H "Authorization: Bearer YOUR_JWT_TOKEN_HERE"
|
||||
```
|
||||
|
||||
## API Endpoints
|
||||
|
||||
### Public Endpoints
|
||||
- `GET /` - API information
|
||||
- `GET /health` - Health check
|
||||
- `POST /register` - Register new user
|
||||
- `POST /login` - Login and get JWT token
|
||||
|
||||
### Protected Endpoints (Require Authentication)
|
||||
|
||||
#### User Management
|
||||
- `GET /me` - Get current user information
|
||||
|
||||
#### Products (All users)
|
||||
- `GET /products` - List products with filtering and search
|
||||
- `POST /products` - Create new product
|
||||
- `GET /products/{product_id}` - Get product with stock info
|
||||
- `PUT /products/{product_id}` - Update product
|
||||
- `DELETE /products/{product_id}` - Delete product and associated stock
|
||||
|
||||
#### Stock Management (All users)
|
||||
- `POST /stock` - Set exact stock quantity
|
||||
- `GET /stock/{gtin}` - Get stock summary by GTIN
|
||||
|
||||
#### CSV Operations (All users)
|
||||
- `POST /import-csv` - Start background CSV import
|
||||
- `GET /import-status/{job_id}` - Check import job status (own jobs only)
|
||||
- `GET /export-csv` - Export products as CSV (streaming)
|
||||
|
||||
#### Statistics (All users)
|
||||
- `GET /stats` - System statistics
|
||||
|
||||
#### Admin-Only Endpoints
|
||||
- `GET /admin/users` - List all users
|
||||
- `PUT /admin/users/{user_id}/status` - Activate/deactivate users
|
||||
|
||||
## User Roles and Permissions
|
||||
|
||||
### Regular Users
|
||||
- Can register and login
|
||||
- Access all product and stock management features
|
||||
- Can import/export CSV files
|
||||
- Can only view their own import jobs
|
||||
- Cannot manage other users
|
||||
|
||||
### Admin Users
|
||||
- All regular user permissions
|
||||
- Can view and manage all users
|
||||
- Can view all import jobs from any user
|
||||
- Can activate/deactivate user accounts
|
||||
|
||||
## Security Features
|
||||
|
||||
### Password Security
|
||||
- Passwords hashed using bcrypt with salt
|
||||
- Minimum password length: 6 characters
|
||||
- No password storage in plaintext anywhere
|
||||
|
||||
### JWT Token Security
|
||||
- Tokens include expiration timestamp
|
||||
- Tokens include user role and permissions
|
||||
- Configurable expiration time (default: 30 minutes)
|
||||
- Secure token validation on each request
|
||||
|
||||
### Rate Limiting
|
||||
- CSV imports: 10 requests per hour
|
||||
- General API: 100 requests per hour per client
|
||||
- Customizable per endpoint
|
||||
|
||||
### Input Validation
|
||||
- All inputs validated using Pydantic models
|
||||
- Email format validation for registration
|
||||
- Username alphanumeric validation
|
||||
- GTIN format validation and normalization
|
||||
|
||||
## Advanced Features
|
||||
|
||||
### Background CSV Import
|
||||
|
||||
Import large CSV files asynchronously:
|
||||
|
||||
```python
|
||||
import requests
|
||||
|
||||
# Start import
|
||||
response = requests.post(
|
||||
'http://localhost:8000/import-csv',
|
||||
headers={'Authorization': 'Bearer YOUR_TOKEN'},
|
||||
json={
|
||||
'url': 'https://example.com/products.csv',
|
||||
'batch_size': 1000
|
||||
}
|
||||
)
|
||||
|
||||
job_id = response.json()['job_id']
|
||||
|
||||
# Check status
|
||||
status_response = requests.get(
|
||||
f'http://localhost:8000/import-status/{job_id}',
|
||||
headers={'Authorization': 'Bearer YOUR_TOKEN'}
|
||||
)
|
||||
print(status_response.json())
|
||||
```
|
||||
|
||||
### Product Search and Filtering
|
||||
|
||||
```bash
|
||||
# Search in title and description
|
||||
GET /products?search=laptop
|
||||
|
||||
# Filter by brand and category
|
||||
GET /products?brand=Apple&category=Electronics
|
||||
|
||||
# Combine filters with pagination
|
||||
GET /products?brand=Samsung&availability=in%20stock&search=phone&skip=0&limit=50
|
||||
```
|
||||
|
||||
### Stock Management
|
||||
|
||||
```bash
|
||||
# Set stock for a specific location
|
||||
curl -X POST "http://localhost:8000/stock" \
|
||||
-H "Authorization: Bearer YOUR_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"gtin": "1234567890123",
|
||||
"location": "WAREHOUSE_A",
|
||||
"quantity": 100
|
||||
}'
|
||||
|
||||
# Get stock summary
|
||||
curl -X GET "http://localhost:8000/stock/1234567890123" \
|
||||
-H "Authorization: Bearer YOUR_TOKEN"
|
||||
```
|
||||
|
||||
## Database Schema
|
||||
|
||||
### Users Table
|
||||
```sql
|
||||
CREATE TABLE users (
|
||||
id SERIAL PRIMARY KEY,
|
||||
email VARCHAR UNIQUE NOT NULL,
|
||||
username VARCHAR UNIQUE NOT NULL,
|
||||
hashed_password VARCHAR NOT NULL,
|
||||
role VARCHAR DEFAULT 'user',
|
||||
is_active BOOLEAN DEFAULT true,
|
||||
last_login TIMESTAMP,
|
||||
created_at TIMESTAMP DEFAULT NOW(),
|
||||
updated_at TIMESTAMP DEFAULT NOW()
|
||||
);
|
||||
```
|
||||
|
||||
### Products Table
|
||||
- Full product catalog with Google Shopping compatibility
|
||||
- Indexed fields: `gtin`, `brand`, `google_product_category`, `availability`
|
||||
- Supports all Google Shopping feed attributes
|
||||
|
||||
### Stock Table
|
||||
- Location-based inventory tracking
|
||||
- GTIN-based product linking
|
||||
- Unique constraint on GTIN+location combinations
|
||||
|
||||
### Import Jobs Table
|
||||
- Track background import operations
|
||||
- User ownership and access control
|
||||
- Status monitoring and error handling
|
||||
|
||||
## Development
|
||||
|
||||
### Running Tests
|
||||
|
||||
```bash
|
||||
# Install test dependencies
|
||||
pip install pytest pytest-asyncio httpx
|
||||
|
||||
# Run all tests
|
||||
pytest
|
||||
|
||||
# Run with coverage
|
||||
pytest --cov=. tests/
|
||||
```
|
||||
|
||||
### Development Server with Auto-reload
|
||||
|
||||
```bash
|
||||
uvicorn main:app --reload --host 0.0.0.0 --port 8000
|
||||
```
|
||||
|
||||
## Production Deployment
|
||||
|
||||
### Security Checklist
|
||||
|
||||
- [ ] Change default admin password immediately
|
||||
- [ ] Set strong JWT_SECRET_KEY (32+ random characters)
|
||||
- [ ] Configure JWT_EXPIRE_MINUTES appropriately
|
||||
- [ ] Set up HTTPS/TLS termination
|
||||
- [ ] Configure CORS for your frontend domains only
|
||||
- [ ] Set up database connection limits and pooling
|
||||
- [ ] Enable request logging and monitoring
|
||||
- [ ] Configure rate limiting per your needs
|
||||
- [ ] Set up user account monitoring and alerting
|
||||
- [ ] Regular security audits of user accounts
|
||||
|
||||
### Environment Variables for Production
|
||||
|
||||
```env
|
||||
# Security
|
||||
JWT_SECRET_KEY=your-very-long-random-secret-key-at-least-32-characters
|
||||
JWT_EXPIRE_MINUTES=30
|
||||
|
||||
# Database (use PostgreSQL in production)
|
||||
DATABASE_URL=postgresql://user:password@db-host:5432/ecommerce_prod
|
||||
|
||||
# Server
|
||||
DEBUG=False
|
||||
API_HOST=0.0.0.0
|
||||
API_PORT=8000
|
||||
|
||||
# Optional: External services
|
||||
REDIS_URL=redis://redis-host:6379/0
|
||||
```
|
||||
|
||||
### Docker Deployment
|
||||
|
||||
```yaml
|
||||
# docker-compose.yml
|
||||
version: '3.8'
|
||||
services:
|
||||
db:
|
||||
image: postgres:15
|
||||
environment:
|
||||
POSTGRES_DB: ecommerce
|
||||
POSTGRES_USER: ecommerce_user
|
||||
POSTGRES_PASSWORD: secure_password
|
||||
volumes:
|
||||
- postgres_data:/var/lib/postgresql/data
|
||||
ports:
|
||||
- "5432:5432"
|
||||
|
||||
api:
|
||||
build: .
|
||||
environment:
|
||||
DATABASE_URL: postgresql://ecommerce_user:secure_password@db:5432/ecommerce
|
||||
JWT_SECRET_KEY: your-production-secret-key
|
||||
JWT_EXPIRE_MINUTES: 30
|
||||
ports:
|
||||
- "8000:8000"
|
||||
depends_on:
|
||||
- db
|
||||
restart: unless-stopped
|
||||
|
||||
volumes:
|
||||
postgres_data:
|
||||
```
|
||||
|
||||
### Nginx Configuration
|
||||
|
||||
```nginx
|
||||
server {
|
||||
listen 80;
|
||||
server_name your-api-domain.com;
|
||||
|
||||
location / {
|
||||
proxy_pass http://localhost:8000;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Authentication Issues
|
||||
|
||||
**Invalid Token Errors:**
|
||||
- Check token expiration (default 30 minutes)
|
||||
- Verify JWT_SECRET_KEY is consistent
|
||||
- Ensure Bearer token format: `Authorization: Bearer <token>`
|
||||
|
||||
**Login Failures:**
|
||||
- Verify username/email and password
|
||||
- Check if user account is active (`is_active: true`)
|
||||
- Review user registration process
|
||||
|
||||
**Permission Denied:**
|
||||
- Confirm user role permissions
|
||||
- Check endpoint access requirements
|
||||
- Verify token contains correct role information
|
||||
|
||||
### Database Issues
|
||||
|
||||
**Connection Errors:**
|
||||
- Verify DATABASE_URL format and credentials
|
||||
- Check database server accessibility
|
||||
- Monitor connection pool limits
|
||||
|
||||
**Migration Issues:**
|
||||
- Tables are created automatically on startup
|
||||
- For schema changes, implement proper migrations
|
||||
- Backup data before major updates
|
||||
|
||||
### Common API Issues
|
||||
|
||||
**CSV Import Failures:**
|
||||
- Check file URL accessibility
|
||||
- Verify CSV format and encoding
|
||||
- Monitor import job status for detailed errors
|
||||
- Ensure proper authentication token
|
||||
|
||||
**Rate Limiting:**
|
||||
- Default limits: 100 requests/hour, 10 CSV imports/hour
|
||||
- Check rate limit headers in responses
|
||||
- Implement proper retry logic with backoff
|
||||
|
||||
### Logging and Monitoring
|
||||
|
||||
Application logs include:
|
||||
- Authentication events (login, failed attempts)
|
||||
- API request/response times
|
||||
- Import job progress and errors
|
||||
- Rate limiting events
|
||||
- Database query performance
|
||||
|
||||
```bash
|
||||
# View logs in development
|
||||
python main.py # Logs to console
|
||||
|
||||
# Docker logs
|
||||
docker-compose logs -f api
|
||||
```
|
||||
|
||||
## Migration from v2.0
|
||||
|
||||
If upgrading from v2.0 to v2.1:
|
||||
|
||||
1. **Install new dependencies:**
|
||||
```bash
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
2. **Update environment variables:**
|
||||
```bash
|
||||
echo "JWT_SECRET_KEY=your-secret-key" >> .env
|
||||
echo "JWT_EXPIRE_MINUTES=30" >> .env
|
||||
```
|
||||
|
||||
3. **Database migration:**
|
||||
The application will automatically create the new `users` table and update the `import_jobs` table on startup.
|
||||
|
||||
4. **Update client code:**
|
||||
- Add authentication to all product management API calls
|
||||
- Implement login flow in your frontend
|
||||
- Handle JWT token refresh
|
||||
|
||||
## Contributing
|
||||
|
||||
1. Fork the repository
|
||||
2. Create a feature branch: `git checkout -b feature-name`
|
||||
3. Make changes with proper tests
|
||||
4. Run security and quality checks
|
||||
5. Update documentation if needed
|
||||
6. Submit a pull request
|
||||
|
||||
### Code Quality Standards
|
||||
|
||||
- All endpoints must have proper authentication
|
||||
- Password handling must use bcrypt
|
||||
- JWT tokens must have expiration
|
||||
- Input validation using Pydantic models
|
||||
- Comprehensive error handling
|
||||
- Unit tests for authentication logic
|
||||
|
||||
## License
|
||||
|
||||
This project is licensed under the MIT License - see the LICENSE file for details.
|
||||
|
||||
## Security
|
||||
|
||||
For security issues, please email the maintainers directly instead of creating a public issue.
|
||||
|
||||
## Support
|
||||
|
||||
For issues and questions:
|
||||
1. Check the troubleshooting section above
|
||||
2. Review existing GitHub issues
|
||||
3. Create a new issue with detailed information including:
|
||||
- Authentication steps you've tried
|
||||
- Error messages and logs
|
||||
- API endpoint and request details
|
||||
- Environment configuration (without secrets)
|
||||
@@ -1,663 +0,0 @@
|
||||
# Letzshop Marketplace API v2.1
|
||||
|
||||
A robust, production-ready FastAPI backend for Luxembourg's premier e-commerce marketplace with multi-vendor support, JWT authentication, and advanced CSV import capabilities.
|
||||
|
||||
## Key Features
|
||||
|
||||
### Marketplace Architecture
|
||||
- **Multi-Vendor Support**: Shops can import and manage their product catalogs independently
|
||||
- **Centralized Product Catalog**: Products exist in main marketplace with shop-specific overrides
|
||||
- **Shop Management**: Complete vendor onboarding, verification, and management system
|
||||
- **Shop-Specific Pricing**: Vendors can set their own prices, availability, and conditions
|
||||
- **Marketplace Controls**: Admin verification and quality control for vendor shops
|
||||
|
||||
### Security & Authentication
|
||||
- **JWT Authentication**: Secure token-based authentication with configurable expiration (30 minutes default)
|
||||
- **User Management**: Registration, login, role-based access control (Admin/User/Shop Owner roles)
|
||||
- **Password Security**: Bcrypt hashing for secure password storage
|
||||
- **Protected Endpoints**: All operations require authentication with proper authorization
|
||||
- **Default Admin Account**: Auto-created admin user for immediate system access
|
||||
|
||||
### Architecture Improvements
|
||||
- **Modular Design**: Separated concerns into utility modules, middleware, and models
|
||||
- **Database Optimization**: Added proper indexing strategy and foreign key relationships
|
||||
- **Connection Pooling**: PostgreSQL support with connection pooling for production scalability
|
||||
- **Background Processing**: Asynchronous CSV import with job tracking per shop
|
||||
|
||||
### Performance Optimizations
|
||||
- **Batch Processing**: CSV imports processed in configurable batches
|
||||
- **Database Indexes**: Strategic indexing for common query patterns including shop relationships
|
||||
- **Streaming Export**: Memory-efficient CSV export for large datasets with shop filtering
|
||||
- **Rate Limiting**: Sliding window rate limiter to prevent API abuse
|
||||
|
||||
### Data Processing
|
||||
- **Robust GTIN Handling**: Centralized GTIN normalization and validation
|
||||
- **Multi-currency Support**: Advanced price parsing with currency extraction
|
||||
- **International Content**: Multi-encoding CSV support for global data
|
||||
- **Shop Association**: Automatic product-shop linking during CSV imports
|
||||
|
||||
## Project Structure
|
||||
|
||||
```
|
||||
letzshop_api/
|
||||
├── main.py # FastAPI application entry point with marketplace support
|
||||
├── models/
|
||||
│ ├── database_models.py # SQLAlchemy ORM models (User, Shop, Product, ShopProduct, Stock, ImportJob)
|
||||
│ └── api_models.py # Pydantic API models with shop and auth models
|
||||
├── utils/
|
||||
│ ├── data_processing.py # GTIN and price processing utilities
|
||||
│ ├── csv_processor.py # CSV import/export handling with shop support
|
||||
│ └── database.py # Database configuration
|
||||
├── middleware/
|
||||
│ ├── auth.py # JWT authentication with bcrypt
|
||||
│ ├── rate_limiter.py # Rate limiting implementation
|
||||
│ ├── error_handler.py # Centralized error handling
|
||||
│ └── logging_middleware.py # Request/response logging
|
||||
├── tests/
|
||||
│ └── test_auth.py # Authentication tests
|
||||
├── requirements.txt # Python dependencies with auth packages
|
||||
└── README.md # This file
|
||||
```
|
||||
|
||||
## Quick Start
|
||||
|
||||
### 1. Installation
|
||||
|
||||
```bash
|
||||
# Clone the repository
|
||||
git clone <repository-url>
|
||||
cd letzshop-api
|
||||
|
||||
# Set up virtual environment
|
||||
python -m venv venv
|
||||
source venv/bin/activate # On Windows: venv\Scripts\activate
|
||||
|
||||
# Install dependencies
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
### 2. Environment Configuration
|
||||
|
||||
Create a `.env` file in the project root:
|
||||
|
||||
```env
|
||||
# Database
|
||||
DATABASE_URL=postgresql://user:password@localhost:5432/letzshop_db
|
||||
# For SQLite (development): DATABASE_URL=sqlite:///./letzshop.db
|
||||
|
||||
# JWT Configuration
|
||||
JWT_SECRET_KEY=your-super-secret-key-change-in-production-immediately
|
||||
JWT_EXPIRE_MINUTES=30
|
||||
|
||||
# Server Configuration
|
||||
API_HOST=0.0.0.0
|
||||
API_PORT=8000
|
||||
DEBUG=False
|
||||
```
|
||||
|
||||
**Important Security Note**: Always change the `JWT_SECRET_KEY` in production!
|
||||
|
||||
### 3. Database Setup
|
||||
|
||||
**For SQLite (Development):**
|
||||
```bash
|
||||
# Run the application - it will create tables automatically
|
||||
python main.py
|
||||
```
|
||||
|
||||
**For PostgreSQL (Production):**
|
||||
```bash
|
||||
# Create PostgreSQL database
|
||||
createdb letzshop_db
|
||||
|
||||
# Run the application - it will create tables and indexes automatically
|
||||
python main.py
|
||||
```
|
||||
|
||||
### 4. Start the Server
|
||||
|
||||
```bash
|
||||
# Development server
|
||||
uvicorn main:app --reload --host 0.0.0.0 --port 8000
|
||||
|
||||
# Production server
|
||||
uvicorn main:app --host 0.0.0.0 --port 8000 --workers 4
|
||||
```
|
||||
|
||||
The API will be available at `http://localhost:8000`
|
||||
|
||||
### 5. Default Access
|
||||
|
||||
The system automatically creates:
|
||||
- **Admin User**: `admin` / `admin123` / `admin@example.com`
|
||||
- **Demo Shop**: `DEMOSHOP` owned by admin for testing
|
||||
|
||||
**Security Warning**: Change the admin password immediately in production!
|
||||
|
||||
## Authentication Flow
|
||||
|
||||
### 1. Register a New User
|
||||
|
||||
```bash
|
||||
curl -X POST "http://localhost:8000/register" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"email": "vendor@example.com",
|
||||
"username": "newvendor",
|
||||
"password": "securepassword123"
|
||||
}'
|
||||
```
|
||||
|
||||
### 2. Login and Get JWT Token
|
||||
|
||||
```bash
|
||||
curl -X POST "http://localhost:8000/login" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"username": "admin",
|
||||
"password": "admin123"
|
||||
}'
|
||||
```
|
||||
|
||||
Response:
|
||||
```json
|
||||
{
|
||||
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
|
||||
"token_type": "bearer",
|
||||
"expires_in": 1800,
|
||||
"user": {
|
||||
"id": 1,
|
||||
"username": "admin",
|
||||
"email": "admin@example.com",
|
||||
"role": "admin",
|
||||
"is_active": true
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Use Token for Protected Endpoints
|
||||
|
||||
```bash
|
||||
curl -X GET "http://localhost:8000/shops" \
|
||||
-H "Authorization: Bearer YOUR_JWT_TOKEN_HERE"
|
||||
```
|
||||
|
||||
## Marketplace Workflow
|
||||
|
||||
### 1. Create a Shop
|
||||
|
||||
```bash
|
||||
curl -X POST "http://localhost:8000/shops" \
|
||||
-H "Authorization: Bearer YOUR_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"shop_code": "TECHSTORE",
|
||||
"shop_name": "Tech Store Luxembourg",
|
||||
"description": "Electronics and gadgets for Luxembourg",
|
||||
"contact_email": "info@techstore.lu",
|
||||
"contact_phone": "+352 123 456 789",
|
||||
"website": "https://techstore.lu"
|
||||
}'
|
||||
```
|
||||
|
||||
### 2. Import Products for Your Shop
|
||||
|
||||
```bash
|
||||
curl -X POST "http://localhost:8000/import-csv" \
|
||||
-H "Authorization: Bearer YOUR_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"url": "https://techstore.com/products.csv",
|
||||
"shop_code": "TECHSTORE",
|
||||
"batch_size": 1000
|
||||
}'
|
||||
```
|
||||
|
||||
### 3. Monitor Import Progress
|
||||
|
||||
```bash
|
||||
curl -X GET "http://localhost:8000/import-status/1" \
|
||||
-H "Authorization: Bearer YOUR_TOKEN"
|
||||
```
|
||||
|
||||
### 4. View Shop Products
|
||||
|
||||
```bash
|
||||
curl -X GET "http://localhost:8000/products?shop_code=TECHSTORE" \
|
||||
-H "Authorization: Bearer YOUR_TOKEN"
|
||||
```
|
||||
|
||||
## API Endpoints
|
||||
|
||||
### Public Endpoints
|
||||
- `GET /` - API information
|
||||
- `GET /health` - Health check
|
||||
- `POST /register` - Register new user
|
||||
- `POST /login` - Login and get JWT token
|
||||
|
||||
### Protected Endpoints (Require Authentication)
|
||||
|
||||
#### User Management
|
||||
- `GET /me` - Get current user information
|
||||
|
||||
#### Shop Management
|
||||
- `POST /shops` - Create new shop
|
||||
- `GET /shops` - List shops with filtering
|
||||
- `GET /shops/{shop_code}` - Get shop details
|
||||
- `PUT /shops/{shop_code}` - Update shop (owners only)
|
||||
- `POST /shops/{shop_code}/products` - Add product to shop catalog
|
||||
- `GET /shops/{shop_code}/products` - Get shop products
|
||||
|
||||
#### Products (Marketplace Catalog)
|
||||
- `GET /products` - List products with filtering (optionally by shop)
|
||||
- `POST /products` - Create new product in marketplace catalog
|
||||
- `GET /products/{product_id}` - Get product with stock info and shop listings
|
||||
- `PUT /products/{product_id}` - Update product
|
||||
- `DELETE /products/{product_id}` - Delete product and associated shop listings
|
||||
|
||||
#### Stock Management
|
||||
- `POST /stock` - Set stock quantity (with optional shop association)
|
||||
- `GET /stock/{gtin}` - Get stock summary by GTIN
|
||||
|
||||
#### CSV Operations
|
||||
- `POST /import-csv` - Start background CSV import for specific shop
|
||||
- `GET /import-status/{job_id}` - Check import job status
|
||||
- `GET /export-csv` - Export products as CSV (optionally filtered by shop)
|
||||
|
||||
#### Statistics
|
||||
- `GET /stats` - Marketplace statistics
|
||||
|
||||
#### Admin-Only Endpoints
|
||||
- `GET /admin/users` - List all users
|
||||
- `PUT /admin/users/{user_id}/status` - Activate/deactivate users
|
||||
- `GET /admin/shops` - List all shops
|
||||
- `PUT /admin/shops/{shop_id}/verify` - Verify/unverify shop
|
||||
- `PUT /admin/shops/{shop_id}/status` - Activate/deactivate shop
|
||||
- `GET /admin/import-jobs` - View all import jobs
|
||||
|
||||
## User Roles and Permissions
|
||||
|
||||
### Regular Users
|
||||
- Can register and login
|
||||
- Can create and manage their own shops
|
||||
- Can import products for their shops
|
||||
- Can manage stock for their products
|
||||
- Can view marketplace products and shops
|
||||
|
||||
### Shop Owners (Regular Users with Shops)
|
||||
- All regular user permissions
|
||||
- Can manage their shop information
|
||||
- Can import/export products for their shops
|
||||
- Can set shop-specific pricing and availability
|
||||
- Can view their import job history
|
||||
|
||||
### Admin Users
|
||||
- All user permissions
|
||||
- Can view and manage all users and shops
|
||||
- Can verify/unverify shops
|
||||
- Can view all import jobs from any shop
|
||||
- Can activate/deactivate user accounts and shops
|
||||
|
||||
## Marketplace Features
|
||||
|
||||
### Shop Verification System
|
||||
- New shops start as unverified
|
||||
- Admin approval required for public visibility
|
||||
- Verified shops appear in public marketplace listings
|
||||
- Quality control through admin verification
|
||||
|
||||
### Multi-Vendor Product Catalog
|
||||
- Products exist in central marketplace catalog
|
||||
- Multiple shops can sell the same product
|
||||
- Shop-specific pricing, availability, and conditions
|
||||
- Automatic product matching during CSV imports
|
||||
|
||||
### Shop-Specific Overrides
|
||||
```json
|
||||
{
|
||||
"product_id": "LAPTOP123",
|
||||
"shop_price": 999.99,
|
||||
"shop_currency": "EUR",
|
||||
"shop_availability": "in stock",
|
||||
"shop_condition": "new",
|
||||
"is_featured": true,
|
||||
"min_quantity": 1,
|
||||
"max_quantity": 5
|
||||
}
|
||||
```
|
||||
|
||||
### Advanced Product Search
|
||||
```bash
|
||||
# Search products in specific shop
|
||||
GET /products?shop_code=TECHSTORE&search=laptop
|
||||
|
||||
# Search across all verified shops
|
||||
GET /products?search=laptop&availability=in%20stock
|
||||
|
||||
# Filter by brand and category
|
||||
GET /products?brand=Apple&category=Electronics
|
||||
```
|
||||
|
||||
## Database Schema
|
||||
|
||||
### Core Tables
|
||||
|
||||
#### Users Table
|
||||
```sql
|
||||
CREATE TABLE users (
|
||||
id SERIAL PRIMARY KEY,
|
||||
email VARCHAR UNIQUE NOT NULL,
|
||||
username VARCHAR UNIQUE NOT NULL,
|
||||
hashed_password VARCHAR NOT NULL,
|
||||
role VARCHAR DEFAULT 'user',
|
||||
is_active BOOLEAN DEFAULT true,
|
||||
last_login TIMESTAMP,
|
||||
created_at TIMESTAMP DEFAULT NOW(),
|
||||
updated_at TIMESTAMP DEFAULT NOW()
|
||||
);
|
||||
```
|
||||
|
||||
#### Shops Table
|
||||
```sql
|
||||
CREATE TABLE shops (
|
||||
id SERIAL PRIMARY KEY,
|
||||
shop_code VARCHAR UNIQUE NOT NULL,
|
||||
shop_name VARCHAR NOT NULL,
|
||||
description TEXT,
|
||||
owner_id INTEGER REFERENCES users(id),
|
||||
contact_email VARCHAR,
|
||||
contact_phone VARCHAR,
|
||||
website VARCHAR,
|
||||
business_address TEXT,
|
||||
tax_number VARCHAR,
|
||||
is_active BOOLEAN DEFAULT true,
|
||||
is_verified BOOLEAN DEFAULT false,
|
||||
created_at TIMESTAMP DEFAULT NOW(),
|
||||
updated_at TIMESTAMP DEFAULT NOW()
|
||||
);
|
||||
```
|
||||
|
||||
#### Products Table
|
||||
- Main marketplace catalog with Google Shopping compatibility
|
||||
- Indexed fields: `gtin`, `brand`, `google_product_category`, `availability`
|
||||
- Supports all Google Shopping feed attributes
|
||||
|
||||
#### ShopProducts Table
|
||||
```sql
|
||||
CREATE TABLE shop_products (
|
||||
id SERIAL PRIMARY KEY,
|
||||
shop_id INTEGER REFERENCES shops(id),
|
||||
product_id INTEGER REFERENCES products(id),
|
||||
shop_product_id VARCHAR,
|
||||
shop_price DECIMAL,
|
||||
shop_sale_price DECIMAL,
|
||||
shop_currency VARCHAR,
|
||||
shop_availability VARCHAR,
|
||||
shop_condition VARCHAR,
|
||||
is_featured BOOLEAN DEFAULT false,
|
||||
is_active BOOLEAN DEFAULT true,
|
||||
min_quantity INTEGER DEFAULT 1,
|
||||
max_quantity INTEGER,
|
||||
created_at TIMESTAMP DEFAULT NOW(),
|
||||
updated_at TIMESTAMP DEFAULT NOW(),
|
||||
UNIQUE(shop_id, product_id)
|
||||
);
|
||||
```
|
||||
|
||||
#### Stock Table
|
||||
- Location-based inventory tracking with optional shop association
|
||||
- GTIN-based product linking
|
||||
- Support for reserved quantities (for order processing)
|
||||
|
||||
#### Import Jobs Table
|
||||
- Track background import operations per shop
|
||||
- User and shop ownership tracking
|
||||
- Status monitoring and error handling
|
||||
|
||||
## Advanced Features
|
||||
|
||||
### Shop-Specific CSV Import
|
||||
|
||||
Import products with automatic shop association:
|
||||
|
||||
```python
|
||||
import requests
|
||||
|
||||
# Start import for specific shop
|
||||
response = requests.post(
|
||||
'http://localhost:8000/import-csv',
|
||||
headers={'Authorization': 'Bearer YOUR_TOKEN'},
|
||||
json={
|
||||
'url': 'https://myshop.com/products.csv',
|
||||
'shop_code': 'MYSHOP',
|
||||
'batch_size': 1000
|
||||
}
|
||||
)
|
||||
|
||||
job_id = response.json()['job_id']
|
||||
|
||||
# Monitor progress
|
||||
status_response = requests.get(
|
||||
f'http://localhost:8000/import-status/{job_id}',
|
||||
headers={'Authorization': 'Bearer YOUR_TOKEN'}
|
||||
)
|
||||
print(status_response.json())
|
||||
```
|
||||
|
||||
### Multi-Shop Product Management
|
||||
|
||||
```bash
|
||||
# Add existing marketplace product to your shop
|
||||
curl -X POST "http://localhost:8000/shops/MYSHOP/products" \
|
||||
-H "Authorization: Bearer YOUR_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"product_id": "EXISTING_PRODUCT_123",
|
||||
"shop_price": 89.99,
|
||||
"shop_availability": "in stock",
|
||||
"is_featured": true
|
||||
}'
|
||||
|
||||
# Get products from specific shop
|
||||
curl -X GET "http://localhost:8000/shops/MYSHOP/products" \
|
||||
-H "Authorization: Bearer YOUR_TOKEN"
|
||||
```
|
||||
|
||||
### Stock Management with Shop Context
|
||||
|
||||
```bash
|
||||
# Set shop-specific stock
|
||||
curl -X POST "http://localhost:8000/stock" \
|
||||
-H "Authorization: Bearer YOUR_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"gtin": "1234567890123",
|
||||
"location": "MYSHOP_WAREHOUSE",
|
||||
"quantity": 50,
|
||||
"shop_code": "MYSHOP"
|
||||
}'
|
||||
```
|
||||
|
||||
## Production Deployment
|
||||
|
||||
### Security Checklist for Marketplace
|
||||
|
||||
- [ ] Change default admin password immediately
|
||||
- [ ] Set strong JWT_SECRET_KEY (32+ random characters)
|
||||
- [ ] Configure JWT_EXPIRE_MINUTES appropriately
|
||||
- [ ] Set up HTTPS/TLS termination
|
||||
- [ ] Configure CORS for your frontend domains only
|
||||
- [ ] Set up database connection limits and pooling
|
||||
- [ ] Enable request logging and monitoring
|
||||
- [ ] Configure rate limiting per your needs
|
||||
- [ ] Set up shop verification workflow
|
||||
- [ ] Implement shop quality monitoring
|
||||
- [ ] Set up automated backup for shop data
|
||||
- [ ] Configure email notifications for shop owners
|
||||
- [ ] Regular security audits of user accounts and shops
|
||||
|
||||
### Environment Variables for Production
|
||||
|
||||
```env
|
||||
# Security
|
||||
JWT_SECRET_KEY=your-very-long-random-secret-key-at-least-32-characters
|
||||
JWT_EXPIRE_MINUTES=30
|
||||
|
||||
# Database (use PostgreSQL in production)
|
||||
DATABASE_URL=postgresql://user:password@db-host:5432/letzshop_prod
|
||||
|
||||
# Server
|
||||
DEBUG=False
|
||||
API_HOST=0.0.0.0
|
||||
API_PORT=8000
|
||||
|
||||
# Marketplace Configuration
|
||||
MARKETPLACE_NAME=Letzshop
|
||||
DEFAULT_CURRENCY=EUR
|
||||
ADMIN_EMAIL=admin@letzshop.lu
|
||||
|
||||
# Optional: External services
|
||||
REDIS_URL=redis://redis-host:6379/0
|
||||
EMAIL_API_KEY=your-email-service-key
|
||||
```
|
||||
|
||||
### Docker Deployment
|
||||
|
||||
```yaml
|
||||
# docker-compose.yml
|
||||
version: '3.8'
|
||||
services:
|
||||
db:
|
||||
image: postgres:15
|
||||
environment:
|
||||
POSTGRES_DB: letzshop
|
||||
POSTGRES_USER: letzshop_user
|
||||
POSTGRES_PASSWORD: secure_password
|
||||
volumes:
|
||||
- postgres_data:/var/lib/postgresql/data
|
||||
ports:
|
||||
- "5432:5432"
|
||||
|
||||
api:
|
||||
build: .
|
||||
environment:
|
||||
DATABASE_URL: postgresql://letzshop_user:secure_password@db:5432/letzshop
|
||||
JWT_SECRET_KEY: your-production-secret-key
|
||||
JWT_EXPIRE_MINUTES: 30
|
||||
MARKETPLACE_NAME: Letzshop
|
||||
ports:
|
||||
- "8000:8000"
|
||||
depends_on:
|
||||
- db
|
||||
restart: unless-stopped
|
||||
|
||||
volumes:
|
||||
postgres_data:
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Marketplace-Specific Issues
|
||||
|
||||
**Shop Import Failures:**
|
||||
- Verify shop exists and is active
|
||||
- Check user permissions for the shop
|
||||
- Ensure CSV format is compatible
|
||||
- Monitor import job status for detailed errors
|
||||
|
||||
**Shop Product Association:**
|
||||
- Products are added to main catalog first
|
||||
- Shop-product relationships created automatically during import
|
||||
- Check shop_products table for associations
|
||||
|
||||
**Permission Issues:**
|
||||
- Shop owners can only manage their own shops
|
||||
- Admin can manage all shops and users
|
||||
- Verify user role and shop ownership
|
||||
|
||||
### Common API Issues
|
||||
|
||||
**Shop Not Found Errors:**
|
||||
- Check shop_code spelling and case (stored uppercase)
|
||||
- Verify shop is active and verified (for public access)
|
||||
- Check user permissions for shop access
|
||||
|
||||
**CSV Import with Shop Code:**
|
||||
- Shop code is required for all imports
|
||||
- Shop must exist before importing
|
||||
- User must have permission to import for that shop
|
||||
|
||||
## Migration Guide
|
||||
|
||||
### From v2.0 to v2.1 (Marketplace Update)
|
||||
|
||||
1. **Backup existing data**
|
||||
2. **Update dependencies:** `pip install -r requirements.txt`
|
||||
3. **Update environment variables** (add shop-related configs)
|
||||
4. **Run application** - new tables will be created automatically
|
||||
5. **Existing products remain in main catalog**
|
||||
6. **Create shops for existing users**
|
||||
7. **Update client applications** to use shop-specific endpoints
|
||||
|
||||
### Data Migration Script Example
|
||||
|
||||
```python
|
||||
# Migrate existing products to demo shop
|
||||
from models.database import Product, Shop, ShopProduct
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
def migrate_to_shops(db: Session):
|
||||
demo_shop = db.query(Shop).filter(Shop.shop_code == "DEMOSHOP").first()
|
||||
products = db.query(Product).all()
|
||||
|
||||
for product in products:
|
||||
shop_product = ShopProduct(
|
||||
shop_id=demo_shop.id,
|
||||
product_id=product.id,
|
||||
shop_price=product.price,
|
||||
shop_availability=product.availability,
|
||||
is_active=True
|
||||
)
|
||||
db.add(shop_product)
|
||||
|
||||
db.commit()
|
||||
```
|
||||
|
||||
## Contributing
|
||||
|
||||
1. Fork the repository
|
||||
2. Create a feature branch: `git checkout -b feature-name`
|
||||
3. Make changes with proper tests
|
||||
4. Run security and quality checks
|
||||
5. Update documentation if needed
|
||||
6. Submit a pull request
|
||||
|
||||
### Code Quality Standards
|
||||
|
||||
- All endpoints must have proper authentication and authorization
|
||||
- Shop ownership verification for protected operations
|
||||
- Input validation using Pydantic models
|
||||
- Comprehensive error handling with meaningful messages
|
||||
- Unit tests for marketplace functionality
|
||||
|
||||
## License
|
||||
|
||||
This project is licensed under the MIT License - see the LICENSE file for details.
|
||||
|
||||
## About Letzshop
|
||||
|
||||
Letzshop is Luxembourg's premier e-commerce marketplace, connecting local and international vendors with Luxembourg customers. Our platform supports multi-vendor operations with advanced inventory management and seamless CSV import capabilities.
|
||||
|
||||
## Support
|
||||
|
||||
For marketplace-specific issues and vendor onboarding:
|
||||
1. Check the troubleshooting section above
|
||||
2. Review existing GitHub issues
|
||||
3. Create a new issue with detailed information including:
|
||||
- Shop code and user information
|
||||
- CSV format and import details
|
||||
- Error messages and logs
|
||||
- Environment configuration (without secrets)
|
||||
|
||||
For vendor support: vendor-support@letzshop.lu
|
||||
For technical issues: tech-support@letzshop.lu
|
||||
Reference in New Issue
Block a user