This comprehensive update transforms Job Forge from a generic MVP concept to a production-ready Python/FastAPI web application prototype with complete documentation, testing infrastructure, and deployment procedures. ## 🏗️ Architecture Changes - Updated all documentation to reflect Python/FastAPI + Dash + PostgreSQL stack - Transformed from MVP concept to deployable web application prototype - Added comprehensive multi-tenant architecture with Row Level Security (RLS) - Integrated Claude API and OpenAI API for AI-powered document generation ## 📚 Documentation Overhaul - **CLAUDE.md**: Complete rewrite as project orchestrator for 4 specialized agents - **README.md**: New centralized documentation hub with organized navigation - **API Specification**: Updated with comprehensive FastAPI endpoint documentation - **Database Design**: Enhanced schema with RLS policies and performance optimization - **Architecture Guide**: Transformed to web application focus with deployment strategy ## 🏗️ New Documentation Structure - **docs/development/**: Python/FastAPI coding standards and development guidelines - **docs/infrastructure/**: Docker setup and server deployment procedures - **docs/testing/**: Comprehensive QA procedures with pytest integration - **docs/ai/**: AI prompt templates and examples (preserved from original) ## 🎯 Team Structure Updates - **.claude/agents/**: 4 new Python/FastAPI specialized agents - simplified_technical_lead.md: Architecture and technical guidance - fullstack_developer.md: FastAPI backend + Dash frontend implementation - simplified_qa.md: pytest testing and quality assurance - simplified_devops.md: Docker deployment and server infrastructure ## 🧪 Testing Infrastructure - **pytest.ini**: Complete pytest configuration with coverage requirements - **tests/conftest.py**: Comprehensive test fixtures and database setup - **tests/unit/**: Example unit tests for auth and application services - **tests/integration/**: API integration test examples - Support for async testing, AI service mocking, and database testing ## 🧹 Cleanup - Removed 9 duplicate/outdated documentation files - Eliminated conflicting technology references (Node.js/TypeScript) - Consolidated overlapping content into comprehensive guides - Cleaned up project structure for professional development workflow ## 🚀 Production Ready Features - Docker containerization for development and production - Server deployment procedures for prototype hosting - Security best practices with JWT authentication and RLS - Performance optimization with database indexing and caching - Comprehensive testing strategy with quality gates This update establishes Job Forge as a professional Python/FastAPI web application prototype ready for development and deployment. 🤖 Generated with Claude Code (https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
325 lines
8.8 KiB
Python
325 lines
8.8 KiB
Python
# Test configuration for Job Forge
|
|
import pytest
|
|
import asyncio
|
|
import asyncpg
|
|
from sqlalchemy.ext.asyncio import AsyncSession, create_async_engine
|
|
from sqlalchemy.orm import sessionmaker
|
|
from fastapi.testclient import TestClient
|
|
from httpx import AsyncClient
|
|
import os
|
|
from typing import AsyncGenerator
|
|
from unittest.mock import AsyncMock
|
|
|
|
from app.main import app
|
|
from app.core.database import get_db, Base
|
|
from app.core.security import create_access_token
|
|
from app.models.user import User
|
|
from app.models.application import Application
|
|
|
|
|
|
# Test database URL
|
|
TEST_DATABASE_URL = os.getenv(
|
|
"TEST_DATABASE_URL",
|
|
"postgresql+asyncpg://jobforge:jobforge123@localhost:5432/jobforge_test"
|
|
)
|
|
|
|
# Test engine and session factory
|
|
test_engine = create_async_engine(TEST_DATABASE_URL, echo=False)
|
|
TestSessionLocal = sessionmaker(
|
|
test_engine, class_=AsyncSession, expire_on_commit=False
|
|
)
|
|
|
|
|
|
@pytest.fixture(scope="session")
|
|
def event_loop():
|
|
"""Create an instance of the default event loop for the test session."""
|
|
loop = asyncio.get_event_loop_policy().new_event_loop()
|
|
yield loop
|
|
loop.close()
|
|
|
|
|
|
@pytest.fixture(scope="session")
|
|
async def setup_test_db():
|
|
"""Set up test database tables."""
|
|
|
|
# Create all tables
|
|
async with test_engine.begin() as conn:
|
|
await conn.run_sync(Base.metadata.drop_all)
|
|
await conn.run_sync(Base.metadata.create_all)
|
|
|
|
# Enable RLS and create policies
|
|
await conn.execute("""
|
|
ALTER TABLE applications ENABLE ROW LEVEL SECURITY;
|
|
|
|
DROP POLICY IF EXISTS applications_user_isolation ON applications;
|
|
CREATE POLICY applications_user_isolation ON applications
|
|
FOR ALL TO authenticated
|
|
USING (user_id = current_setting('app.current_user_id')::UUID);
|
|
|
|
-- Create vector extension if needed
|
|
CREATE EXTENSION IF NOT EXISTS vector;
|
|
""")
|
|
|
|
yield
|
|
|
|
# Cleanup
|
|
async with test_engine.begin() as conn:
|
|
await conn.run_sync(Base.metadata.drop_all)
|
|
|
|
|
|
@pytest.fixture
|
|
async def test_db(setup_test_db) -> AsyncGenerator[AsyncSession, None]:
|
|
"""Create a test database session."""
|
|
|
|
async with TestSessionLocal() as session:
|
|
try:
|
|
yield session
|
|
finally:
|
|
await session.rollback()
|
|
|
|
|
|
@pytest.fixture
|
|
def override_get_db(test_db: AsyncSession):
|
|
"""Override the get_db dependency for testing."""
|
|
|
|
def _override_get_db():
|
|
return test_db
|
|
|
|
app.dependency_overrides[get_db] = _override_get_db
|
|
yield
|
|
app.dependency_overrides.clear()
|
|
|
|
|
|
@pytest.fixture
|
|
def test_client(override_get_db):
|
|
"""Create a test client."""
|
|
|
|
with TestClient(app) as client:
|
|
yield client
|
|
|
|
|
|
@pytest.fixture
|
|
async def async_client(override_get_db):
|
|
"""Create an async test client."""
|
|
|
|
async with AsyncClient(app=app, base_url="http://test") as client:
|
|
yield client
|
|
|
|
|
|
@pytest.fixture
|
|
async def test_user(test_db: AsyncSession):
|
|
"""Create a test user."""
|
|
|
|
from app.crud.user import create_user
|
|
from app.schemas.user import UserCreate
|
|
|
|
user_data = UserCreate(
|
|
email="test@jobforge.com",
|
|
password="testpassword123",
|
|
first_name="Test",
|
|
last_name="User"
|
|
)
|
|
|
|
user = await create_user(test_db, user_data)
|
|
await test_db.commit()
|
|
return user
|
|
|
|
|
|
@pytest.fixture
|
|
def test_user_token(test_user):
|
|
"""Create a JWT token for test user."""
|
|
|
|
token_data = {"sub": str(test_user.id), "email": test_user.email}
|
|
return create_access_token(data=token_data)
|
|
|
|
|
|
@pytest.fixture
|
|
async def test_application(test_db: AsyncSession, test_user):
|
|
"""Create a test job application."""
|
|
|
|
from app.crud.application import create_application
|
|
from app.schemas.application import ApplicationCreate
|
|
|
|
app_data = ApplicationCreate(
|
|
company_name="Test Corp",
|
|
role_title="Software Developer",
|
|
job_description="Python developer position with FastAPI experience",
|
|
status="draft"
|
|
)
|
|
|
|
application = await create_application(test_db, app_data, test_user.id)
|
|
await test_db.commit()
|
|
return application
|
|
|
|
|
|
@pytest.fixture
|
|
def mock_claude_service():
|
|
"""Mock Claude AI service."""
|
|
|
|
mock = AsyncMock()
|
|
mock.generate_cover_letter.return_value = """
|
|
Dear Hiring Manager,
|
|
|
|
I am writing to express my strong interest in the Software Developer position at Test Corp.
|
|
With my experience in Python development and FastAPI expertise, I am confident I would be
|
|
a valuable addition to your team.
|
|
|
|
Thank you for your consideration.
|
|
|
|
Sincerely,
|
|
Test User
|
|
"""
|
|
|
|
return mock
|
|
|
|
|
|
@pytest.fixture
|
|
def mock_openai_service():
|
|
"""Mock OpenAI service."""
|
|
|
|
mock = AsyncMock()
|
|
mock.create_embedding.return_value = [0.1] * 1536 # Mock embedding vector
|
|
mock.test_connection.return_value = True
|
|
|
|
return mock
|
|
|
|
|
|
@pytest.fixture
|
|
async def multiple_test_users(test_db: AsyncSession):
|
|
"""Create multiple test users for isolation testing."""
|
|
|
|
from app.crud.user import create_user
|
|
from app.schemas.user import UserCreate
|
|
|
|
users = []
|
|
for i in range(3):
|
|
user_data = UserCreate(
|
|
email=f"user{i}@test.com",
|
|
password="password123",
|
|
first_name=f"User{i}",
|
|
last_name="Test"
|
|
)
|
|
user = await create_user(test_db, user_data)
|
|
users.append(user)
|
|
|
|
await test_db.commit()
|
|
return users
|
|
|
|
|
|
@pytest.fixture
|
|
async def applications_for_users(test_db: AsyncSession, multiple_test_users):
|
|
"""Create applications for multiple users to test isolation."""
|
|
|
|
from app.crud.application import create_application
|
|
from app.schemas.application import ApplicationCreate
|
|
|
|
all_applications = []
|
|
|
|
for i, user in enumerate(multiple_test_users):
|
|
for j in range(2): # 2 applications per user
|
|
app_data = ApplicationCreate(
|
|
company_name=f"Company{i}-{j}",
|
|
role_title=f"Role{i}-{j}",
|
|
job_description=f"Job description for user {i}, application {j}",
|
|
status="draft"
|
|
)
|
|
application = await create_application(test_db, app_data, user.id)
|
|
all_applications.append(application)
|
|
|
|
await test_db.commit()
|
|
return all_applications
|
|
|
|
|
|
# Test data factories
|
|
class TestDataFactory:
|
|
"""Factory for creating test data."""
|
|
|
|
@staticmethod
|
|
def user_data(email: str = None, **kwargs):
|
|
"""Create user test data."""
|
|
return {
|
|
"email": email or "test@example.com",
|
|
"password": "testpassword123",
|
|
"first_name": "Test",
|
|
"last_name": "User",
|
|
**kwargs
|
|
}
|
|
|
|
@staticmethod
|
|
def application_data(company_name: str = None, **kwargs):
|
|
"""Create application test data."""
|
|
return {
|
|
"company_name": company_name or "Test Company",
|
|
"role_title": "Software Developer",
|
|
"job_description": "Python developer position",
|
|
"status": "draft",
|
|
**kwargs
|
|
}
|
|
|
|
@staticmethod
|
|
def ai_response():
|
|
"""Create mock AI response."""
|
|
return """
|
|
Dear Hiring Manager,
|
|
|
|
I am excited to apply for this position. My background in software development
|
|
and passion for technology make me an ideal candidate.
|
|
|
|
Best regards,
|
|
Test User
|
|
"""
|
|
|
|
|
|
# Database utilities for testing
|
|
async def create_test_user_and_token(db: AsyncSession, email: str):
|
|
"""Helper to create a user and return auth token."""
|
|
|
|
from app.crud.user import create_user
|
|
from app.schemas.user import UserCreate
|
|
|
|
user_data = UserCreate(
|
|
email=email,
|
|
password="password123",
|
|
first_name="Test",
|
|
last_name="User"
|
|
)
|
|
|
|
user = await create_user(db, user_data)
|
|
await db.commit()
|
|
|
|
token_data = {"sub": str(user.id), "email": user.email}
|
|
token = create_access_token(data=token_data)
|
|
|
|
return user, token
|
|
|
|
|
|
async def set_rls_context(db: AsyncSession, user_id: str):
|
|
"""Set RLS context for testing multi-tenancy."""
|
|
|
|
await db.execute(f"SET app.current_user_id = '{user_id}'")
|
|
|
|
|
|
# Performance testing helpers
|
|
@pytest.fixture
|
|
def benchmark_db_operations():
|
|
"""Benchmark database operations."""
|
|
|
|
import time
|
|
|
|
class BenchmarkContext:
|
|
def __init__(self):
|
|
self.start_time = None
|
|
self.end_time = None
|
|
|
|
def __enter__(self):
|
|
self.start_time = time.time()
|
|
return self
|
|
|
|
def __exit__(self, *args):
|
|
self.end_time = time.time()
|
|
|
|
@property
|
|
def duration(self):
|
|
return self.end_time - self.start_time if self.end_time else None
|
|
|
|
return BenchmarkContext |