Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
255 changes: 255 additions & 0 deletions TESTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,255 @@
# πŸ§ͺ Testing Guide

This document explains how to run tests for the ApplyBot application.

## πŸš€ Quick Start

1. **Install dependencies:**
```bash
pip install -r requirements.txt
```

2. **Run all tests:**
```bash
python run_tests.py
```

## πŸ“ Test Structure

- `conftest.py` - Pytest configuration with fixtures and mocks
- `test_integration_simple.py` - Integration tests for API endpoints
- `run_tests.py` - Test runner script with different options
- `TESTING.md` - This documentation file

## πŸ”§ Environment Setup

### Environment Variables

Tests automatically load environment variables from `.env` file. Required variables:

```bash
# Database
DATABASE_URL=sqlite:///test.db

# AI Services
OPENAI_API_KEY=your-openai-key

# Supabase
SUPABASE_URL=https://your-project.supabase.co
SUPABASE_KEY=your-supabase-key

# Optional - will use test defaults if not provided
REDIS_URL=redis://localhost:6379/1
REED_API_KEY=your-reed-key
ADZUNA_APP_ID=your-adzuna-id
ADZUNA_APP_KEY=your-adzuna-key
```

### Test Environment Variables

If environment variables are not set, `conftest.py` automatically provides test defaults:

- `DATABASE_URL`: `sqlite:///test.db`
- `REDIS_URL`: `redis://localhost:6379/1`
- `OPENAI_API_KEY`: `test-openai-key`
- `SUPABASE_URL`: `https://test.supabase.co`
- `SUPABASE_KEY`: `test-supabase-key`
- `ENVIRONMENT`: `test`

## 🎯 Running Tests

### Using the Test Runner

```bash
# Run all tests
python run_tests.py

# Run only integration tests
python run_tests.py integration

# Run with coverage report
python run_tests.py coverage

# Test environment variable loading
python run_tests.py env

# Show help
python run_tests.py --help
```

### Using Pytest Directly

```bash
# Run all tests with verbose output
pytest -v

# Run specific test file
pytest test_integration_simple.py -v

# Run specific test class
pytest test_integration_simple.py::TestHealthEndpoint -v

# Run specific test method
pytest test_integration_simple.py::TestHealthEndpoint::test_health_check -v

# Run with coverage
pytest --cov=app --cov-report=html
```

## πŸ” Test Categories

### Integration Tests (`test_integration_simple.py`)

Tests API endpoints with mocked external dependencies:

- **Health Check**: Basic server health endpoint
- **Job Endpoints**: Job fetching, filtering, and sources
- **Project Matching**: Project-to-job matching algorithms
- **Resume Generation**: PDF resume creation with AI
- **Cover Letter Generation**: AI-powered cover letter creation
- **Cache Endpoints**: Redis cache statistics
- **Environment Variables**: Proper loading and defaults

### Mocked Dependencies

Tests use mocks for external services to avoid:
- Real API calls to OpenAI, Reed, Adzuna
- Database connections (uses test database)
- Redis connections (mocked)
- File system operations

## πŸ› οΈ Fixtures Available

From `conftest.py`:

- `mock_openai_client` - Mocked OpenAI API client
- `mock_supabase_client` - Mocked Supabase client
- `mock_redis_client` - Mocked Redis client
- `mock_external_apis` - Mocked job API responses
- `sample_job_data` - Sample job data for testing
- `sample_user_data` - Sample user data for testing
- `sample_project_data` - Sample project data for testing

## πŸ“Š Coverage Reports

Generate HTML coverage reports:

```bash
python run_tests.py coverage
```

View the report:
```bash
open htmlcov/index.html # macOS
xdg-open htmlcov/index.html # Linux
```

## πŸ› Troubleshooting

### Common Issues

1. **Import Errors**
```bash
# Make sure you're in the project root
cd /path/to/ApplyBot

# Install dependencies
pip install -r requirements.txt
```

2. **Environment Variable Issues**
```bash
# Test environment loading specifically
python run_tests.py env
```

3. **API Server Not Running**
- Integration tests will skip if the API server isn't running
- Start the server: `python start_server.py`
- Or run tests without server dependency

4. **Database Issues**
```bash
# Tests use SQLite by default
# Make sure DATABASE_URL is set correctly
export DATABASE_URL="sqlite:///test.db"
```

### Debug Mode

Run tests with more verbose output:

```bash
pytest -v -s --tb=long
```

## πŸ”„ Continuous Integration

For CI/CD pipelines, use:

```bash
# Install dependencies
pip install -r requirements.txt

# Run tests with JUnit XML output
pytest --junitxml=test-results.xml

# Run with coverage for CI
pytest --cov=app --cov-report=xml --cov-report=term
```

## πŸ“ Writing New Tests

### Test File Naming
- Integration tests: `test_integration_*.py`
- Unit tests: `test_unit_*.py`
- Specific feature tests: `test_feature_*.py`

### Example Test Structure

```python
import pytest
from unittest.mock import patch

class TestNewFeature:
\"\"\"Test new feature functionality.\"\"\"

def test_basic_functionality(self, sample_data):
\"\"\"Test basic feature works.\"\"\"
# Arrange
input_data = sample_data

# Act
result = your_function(input_data)

# Assert
assert result is not None
assert result["status"] == "success"

@patch('external.service.call')
def test_with_mocked_service(self, mock_service):
\"\"\"Test with mocked external service.\"\"\"
# Setup mock
mock_service.return_value = {"data": "test"}

# Test your code
result = function_that_calls_service()

# Verify
assert mock_service.called
assert result["data"] == "test"
```

## 🎯 Best Practices

1. **Use Fixtures**: Leverage existing fixtures for common test data
2. **Mock External Services**: Always mock external API calls
3. **Test Edge Cases**: Include error conditions and edge cases
4. **Clear Test Names**: Use descriptive test method names
5. **Arrange-Act-Assert**: Structure tests clearly
6. **Independent Tests**: Each test should be independent
7. **Clean Up**: Use fixtures for setup/teardown when needed

---

For more information, see the main [README.md](README.md) and [API_DOCUMENTATION.md](API_DOCUMENTATION.md).
137 changes: 137 additions & 0 deletions conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
"""
Pytest configuration file for ApplyBot tests.
Handles environment variable loading and mock configurations.
"""

import os
import pytest
from unittest.mock import patch, MagicMock
from dotenv import load_dotenv

# Load environment variables from .env file
load_dotenv()

@pytest.fixture(scope="session", autouse=True)
def setup_test_environment():
"""Set up test environment with mock configurations."""
# Set test environment variables if not already set
test_env_vars = {
"DATABASE_URL": "sqlite:///test.db",
"REDIS_URL": "redis://localhost:6379/1",
"OPENAI_API_KEY": "test-openai-key",
"SUPABASE_URL": "https://test.supabase.co",
"SUPABASE_KEY": "test-supabase-key",
"REED_API_KEY": "test-reed-key",
"ADZUNA_APP_ID": "test-adzuna-id",
"ADZUNA_APP_KEY": "test-adzuna-key",
"ENVIRONMENT": "test"
}

for key, value in test_env_vars.items():
if not os.getenv(key):
os.environ[key] = value

@pytest.fixture
def mock_openai_client():
"""Mock OpenAI client for testing."""
with patch('openai.OpenAI') as mock_client:
mock_instance = MagicMock()
mock_client.return_value = mock_instance

# Mock chat completions
mock_instance.chat.completions.create.return_value = MagicMock(
choices=[MagicMock(message=MagicMock(content="Mock AI response"))]
)

yield mock_instance

@pytest.fixture
def mock_supabase_client():
"""Mock Supabase client for testing."""
with patch('supabase.create_client') as mock_create:
mock_client = MagicMock()
mock_create.return_value = mock_client

# Mock table operations
mock_client.table.return_value.select.return_value.execute.return_value = MagicMock(
data=[]
)
mock_client.table.return_value.insert.return_value.execute.return_value = MagicMock(
data=[{"id": "test-id"}]
)

yield mock_client

@pytest.fixture
def mock_redis_client():
"""Mock Redis client for testing."""
with patch('redis.Redis') as mock_redis:
mock_instance = MagicMock()
mock_redis.return_value = mock_instance

# Mock Redis operations
mock_instance.get.return_value = None
mock_instance.set.return_value = True
mock_instance.ping.return_value = True

yield mock_instance

@pytest.fixture
def mock_external_apis():
"""Mock external job APIs (Reed, Adzuna, etc.)."""
with patch('requests.get') as mock_get:
mock_response = MagicMock()
mock_response.status_code = 200
mock_response.json.return_value = {
"results": [
{
"id": "test-job-1",
"title": "Test Developer",
"company": "Test Company",
"location": "Test Location",
"description": "Test job description"
}
]
}
mock_get.return_value = mock_response

yield mock_get

@pytest.fixture
def sample_job_data():
"""Sample job data for testing."""
return {
"id": "test-job-uuid",
"title": "Senior Python Developer",
"company": "TechCorp",
"description": "We are looking for a senior Python developer...",
"location": "San Francisco, CA",
"salary_range": "$120k - $150k",
"requirements": ["Python", "FastAPI", "PostgreSQL"],
"source": "RemoteOK",
"posted_date": "2024-01-15T10:30:00Z"
}

@pytest.fixture
def sample_user_data():
"""Sample user data for testing."""
return {
"user_id": "test-user-uuid",
"name": "John Doe",
"email": "john@example.com",
"phone": "+1(555) 123-4567",
"location": "San Francisco, CA",
"experience_years": "5+",
"primary_skills": ["Python", "React", "AWS"]
}

@pytest.fixture
def sample_project_data():
"""Sample project data for testing."""
return {
"project_id": "test-project-uuid",
"title": "E-commerce Platform",
"description": "Built a full-stack e-commerce platform using Python, FastAPI, and React",
"technologies": ["Python", "FastAPI", "React", "PostgreSQL"],
"user_id": "test-user-uuid"
}
Loading