Testing Framework
The Corgi Recommender Service employs a comprehensive, multi-layered testing strategy to ensure reliability, performance, and security. Our commitment to quality is reflected in our 100% test pass requirement—every test must succeed before code can be merged.
Testing Philosophy
Zero-Tolerance Quality Gate
Our CI/CD pipeline enforces a strict quality gate:
- 100% Test Success Required: All 396+ tests must pass
- No Performance Regressions: Tests must complete within established thresholds
- Security First: Automated vulnerability scanning on every commit
- Continuous Validation: Automated health checks and browser testing
This approach ensures that the main branch is always in a deployable state.
Test Categories
We organize tests into distinct categories for targeted execution:
# pytest markers from pytest.ini
markers =
unit: Core functionality tests
integration: Cross-component tests
performance: Speed and resource tests
security: Vulnerability tests
api: REST API endpoint tests
db: Database operation tests
auth: Authentication flow tests
privacy: Data protection tests
Unit Testing
Running Unit Tests
Unit tests verify individual components in isolation:
# Run all unit tests
pytest -m unit
# Run specific test file
pytest tests/test_recommendations.py
# Run with coverage report
pytest --cov=. --cov-report=term-missing
# Run tests in parallel for speed
pytest -n auto
Writing Unit Tests
Follow our conventions for consistency:
# tests/test_example.py
import pytest
from unittest.mock import Mock, patch
class TestRecommendationEngine:
"""Test suite for recommendation engine."""
@pytest.fixture
def mock_user(self):
"""Fixture for test user data."""
return {
'id': 'test_user_123',
'preferences': ['technology', 'science']
}
def test_generate_recommendations(self, mock_user):
"""Test recommendation generation for a user."""
# Arrange
from core.ranking_algorithm import generate_recommendations
expected_count = 20
# Act
recommendations = generate_recommendations(
user_id=mock_user['id'],
limit=expected_count
)
# Assert
assert len(recommendations) == expected_count
assert all(0 <= r.score <= 1 for r in recommendations)
assert recommendations == sorted(
recommendations,
key=lambda x: x.score,
reverse=True
)
Test Naming Convention
Use descriptive test names that explain what is being tested:
- test_<function>_<scenario>_<expected_result>
- Example: test_authentication_with_expired_token_returns_401
Integration Testing
API Integration Tests
Test complete API flows end-to-end:
# Run all API integration tests
pytest -m api
# Run with real database (requires setup)
POSTGRES_DB=corgi_test pytest tests/test_api_flow.py
# Test specific endpoint group
pytest tests/test_mastodon_api.py -v
Database Integration Tests
Verify database operations and migrations:
# Run database tests
pytest -m db
# Test with specific database
pytest tests/test_db_connection.py --db-url postgresql://localhost/corgi_test
Example Integration Test
# tests/test_api_integration.py
@pytest.mark.integration
@pytest.mark.api
class TestRecommendationAPI:
"""Integration tests for recommendation API."""
@pytest.fixture
def client(self):
"""Create test client."""
from app import app
app.config['TESTING'] = True
with app.test_client() as client:
yield client
def test_get_recommendations_flow(self, client):
"""Test complete recommendation flow."""
# Authenticate
auth_response = client.post('/api/v1/oauth/token', json={
'grant_type': 'authorization_code',
'code': 'test_code'
})
assert auth_response.status_code == 200
token = auth_response.json['access_token']
# Get recommendations
rec_response = client.get(
'/api/v1/recommendations',
headers={'Authorization': f'Bearer {token}'}
)
assert rec_response.status_code == 200
assert 'recommendations' in rec_response.json
Performance Testing
Benchmark Tests
Monitor performance across different load scenarios:
# Run performance benchmarks
pytest -m performance
# Run specific benchmark suite
pytest tests/test_performance_benchmarks.py -v
# Generate performance report
pytest tests/test_performance_monitoring.py --benchmark-json=perf.json
Performance Thresholds
Our performance tests enforce strict thresholds:
| Metric | Light Load | Standard Load | Heavy Load |
|---|---|---|---|
| P95 Latency | < 50ms | < 100ms | < 200ms |
| Throughput | > 50 RPS | > 25 RPS | > 15 RPS |
| Success Rate | > 99% | > 98% | > 95% |
Load Testing
Use Locust for realistic load testing:
# Run load test
locust -f tests/locustfile_recommendations.py --host=http://localhost:5002
# Headless load test with specific parameters
locust -f tests/locustfile_recommendations.py \
--headless \
--users 100 \
--spawn-rate 10 \
--run-time 5m
Security Testing
Automated Security Scans
Security testing runs automatically in CI:
# Run all security tests
make security-scan
# Individual security tools
pip-audit # Check for vulnerable dependencies
bandit -r . -ll # Static security analysis
safety check # Additional vulnerability database
semgrep --config=auto . # Advanced pattern matching
Security Test Example
# tests/test_security_interactions.py
@pytest.mark.security
class TestSecurityFeatures:
"""Test security controls and protections."""
def test_sql_injection_prevention(self, client):
"""Verify SQL injection attempts are blocked."""
malicious_input = "'; DROP TABLE users; --"
response = client.get(
f'/api/v1/search?q={malicious_input}',
headers={'Authorization': 'Bearer valid_token'}
)
# Should sanitize input, not execute SQL
assert response.status_code in [200, 400]
assert 'error' not in response.json
# Verify database is intact
with get_db_connection() as conn:
cursor = conn.cursor()
cursor.execute("SELECT 1 FROM users LIMIT 1")
assert cursor.fetchone() is not None
End-to-End Browser Testing
Intelligent Browser Agent
Our browser testing uses Playwright for realistic user simulation:
# Run browser tests (headless)
make dev-test
# Run with visible browser for debugging
make dev-test-headed
# Continuous browser monitoring
make dev-test-continuous
Key Browser Tests
The browser agent validates critical user flows:
- API Connection Test: Ensures frontend connects to backend
- OAuth Flow Test: Validates authentication process
- Recommendation Display: Confirms recommendations appear in UI
# Example from browser_agent.py
async def test_elk_corgi_connection(self, page: Page) -> TestResult:
"""Critical test: Check if ELK frontend can connect to Corgi API."""
# Navigate to frontend
await page.goto(self.frontend_url)
# Check for offline mode (FAIL condition)
offline_messages = [
msg for msg in self.console_messages
if "[Corgi] Running in offline mode" in msg.text
]
if offline_messages:
return TestResult(
name="ELK-Corgi API Connection",
passed=False,
error="API CONNECTION BROKEN: Frontend in offline mode!"
)
Validation Framework
Comprehensive System Validation
The validation framework checks system integrity:
# Run full validation suite
make validate
# Dry run (no side effects)
make dry-validate
# Quick health check
make check
# Nightly comprehensive check
make nightly-check
Validation Components
- API Endpoint Validation: All endpoints respond correctly
- Database Integrity: Schema and data consistency
- Configuration Validation: Environment and settings
- Documentation Validation: API docs match implementation
Continuous Testing
Automated Development Workflow
During development, tests run automatically:
# Start development with monitoring
make dev
# This enables:
# - Continuous health checks every 30 seconds
# - Browser testing on file changes
# - Real-time error detection
# - Screenshot capture on failures
Pre-Commit Testing
Git hooks ensure quality before commits:
#!/bin/sh
# .git/hooks/pre-commit
echo "Running health check before commit..."
./dev-monitor health
if [ $? -ne 0 ]; then
echo "Health check failed! Fix issues before committing."
exit 1
fi
Test Organization
Directory Structure
Tests mirror the source code structure:
tests/
├── conftest.py # Shared fixtures
├── test_*.py # Test files
├── fixtures/ # Test data and helpers
├── config_tests/ # Configuration tests
└── performance_baseline.md # Performance benchmarks
Naming Conventions
- Test Files:
test_<module>.py - Test Classes:
Test<Component> - Test Methods:
test_<scenario>_<expected_result> - Fixtures:
mock_<resource>orfake_<data>
Running Tests in CI/CD
Quality Gate Workflow
The CI pipeline runs comprehensive checks:
# From .github/workflows/quality-gate.yml
- name: Run full test suite (ZERO FAILURES REQUIRED)
run: |
python -m pytest \
--tb=short \
--cov=. \
--cov-report=xml \
--timeout=300 \
--maxfail=1 \
-v
Test Execution Strategy
- Parallel Execution: Tests run on Python 3.11 and 3.12
- Fast Fail: Stops on first failure to save time
- Timeout Protection: 300-second limit per test
- Coverage Tracking: Uploaded to Codecov
Best Practices
Writing Effective Tests
- Isolation: Each test should be independent
- Clarity: Test one thing per test method
- Speed: Mock external dependencies
- Reliability: No flaky tests allowed
- Coverage: Aim for critical path coverage
Using Fixtures
@pytest.fixture
def authenticated_client(client):
"""Client with valid authentication."""
client.headers['Authorization'] = 'Bearer test_token'
client.headers['X-Mastodon-Instance'] = 'mastodon.social'
return client
def test_protected_endpoint(authenticated_client):
"""Test endpoint requires authentication."""
response = authenticated_client.get('/api/v1/user/profile')
assert response.status_code == 200
Mocking Best Practices
@patch('external_service.api_call')
def test_with_mock(mock_api):
"""Test with mocked external service."""
# Configure mock
mock_api.return_value = {'status': 'success'}
# Test your code
result = your_function()
# Verify mock was called correctly
mock_api.assert_called_once_with(expected_params)
Troubleshooting
Common Issues
Tests Failing Locally but Passing in CI
- Check environment variables: cat .env.test
- Ensure test database is clean: make reset-db
- Verify dependencies match: pip freeze | diff requirements.txt -
Slow Test Execution
- Run in parallel: pytest -n auto
- Skip slow tests temporarily: pytest -m "not slow"
- Profile test execution: pytest --durations=10
Flaky Tests
- Add retry logic: @pytest.mark.flaky(reruns=3)
- Increase timeouts for network operations
- Use proper test isolation
Summary
The Corgi testing framework ensures code quality through:
- Comprehensive Coverage: Unit, integration, performance, and security tests
- Automated Execution: CI/CD pipeline with strict quality gates
- Continuous Monitoring: Real-time testing during development
- Clear Standards: Consistent naming and organization
- Zero Tolerance: 100% test success requirement
By following these testing practices, we maintain a robust, reliable recommendation service that users can trust.
Quick Test Commands
pytest- Run all testsmake validate- Full system validationmake dev-test- Browser testingmake check- Quick health check