This document describes the testing infrastructure and guidelines for VLog.
tests/
├── conftest.py # Shared fixtures and test database setup
├── test_*.py # Test modules organized by component
│
├── Unit Tests (Component-specific)
│ ├── test_admin_api.py # Admin API endpoints
│ ├── test_public_api.py # Public API endpoints
│ ├── test_worker_api.py # Worker API endpoints
│ ├── test_cli.py # CLI command tests
│ ├── test_transcoder.py # Transcoding logic
│ ├── test_hwaccel.py # Hardware acceleration
│ └── test_*.py # Other component tests
│
├── Integration Tests
│ ├── test_transcoder_integration.py # Transcoder pipeline integration
│ ├── test_worker_integration.py # Worker job flow integration
│ ├── test_analytics_caching_integration.py
│ └── test_remote_transcoder.py # Remote worker lifecycle
│
└── End-to-End Tests
├── test_e2e_upload.py # Complete upload workflow
├── test_workflows.py # Multi-component workflows
└── test_migrations.py # Database migration tests
-
PostgreSQL: Tests require a PostgreSQL database server
# Configure test database connection export VLOG_TEST_PG_HOST=localhost export VLOG_TEST_PG_PORT=5432 export VLOG_TEST_PG_USER=vlog export VLOG_TEST_PG_PASSWORD=vlog_password
-
Python Dependencies: Install test dependencies
pip install -e . pip install pytest pytest-asyncio pytest-cov -
Test Mode: Always set test mode environment variable
export VLOG_TEST_MODE=1
# Run all tests with coverage
VLOG_TEST_MODE=1 pytest
# Run with coverage report
VLOG_TEST_MODE=1 pytest --cov=api --cov=worker --cov=cli --cov-report=html
# Run with verbose output
VLOG_TEST_MODE=1 pytest -v# Run a single test file
VLOG_TEST_MODE=1 pytest tests/test_public_api.py
# Run a specific test class
VLOG_TEST_MODE=1 pytest tests/test_admin_api.py::TestVideoUpload
# Run a specific test
VLOG_TEST_MODE=1 pytest tests/test_admin_api.py::TestVideoUpload::test_upload_video
# Run tests matching a pattern
VLOG_TEST_MODE=1 pytest -k "upload"# Run only integration tests
VLOG_TEST_MODE=1 pytest -m integration
# Run only end-to-end tests
VLOG_TEST_MODE=1 pytest -m e2e
# Exclude slow tests
VLOG_TEST_MODE=1 pytest -m "not slow"- Line coverage: 80%+ (current baseline)
- Branch coverage: 70%+
- Critical paths: 100% (upload, transcode, playback)
After running tests with coverage:
# View HTML report
open htmlcov/index.html # macOS
xdg-open htmlcov/index.html # Linux
# View terminal summary
VLOG_TEST_MODE=1 pytest --cov-report=term-missing# Check coverage for specific module
VLOG_TEST_MODE=1 pytest --cov=api.admin --cov-report=term
# Check coverage with branch analysis
VLOG_TEST_MODE=1 pytest --cov=api --cov-report=term --cov-branchCommon fixtures are defined in tests/conftest.py:
test_database: Async database connection with fresh schematest_storage: Temporary directories for video filesadmin_client: Test client for admin APIpublic_client: Test client for public APIworker_client: Test client for worker APIsample_category: Pre-created categorysample_pending_video: Video in pending statesample_video: Video in ready stateregistered_worker: Pre-registered worker with API key
class TestFeatureName:
"""Test description."""
@pytest.mark.asyncio
async def test_specific_behavior(self, fixture1, fixture2):
"""Test that specific behavior works as expected."""
# Arrange
setup_data()
# Act
result = await perform_action()
# Assert
assert result == expected# Mark as integration test
@pytest.mark.integration
async def test_integration_workflow():
pass
# Mark as end-to-end test
@pytest.mark.e2e
async def test_full_system_workflow():
pass
# Mark as slow test
@pytest.mark.slow
async def test_large_file_processing():
passimport pytest
@pytest.mark.asyncio
async def test_async_function(test_database):
"""Test async database operations."""
result = await test_database.fetch_one(query)
assert result is not Nonedef test_api_endpoint(admin_client):
"""Test API endpoint response."""
response = admin_client.post(
"/api/videos",
files={"file": ("test.mp4", io.BytesIO(b"content"), "video/mp4")},
data={"title": "Test Video"}
)
assert response.status_code == 200
data = response.json()
assert data["status"] == "ok"
assert "video_id" in datadef test_error_handling(admin_client):
"""Test that errors are handled appropriately."""
# Test missing required field
response = admin_client.post("/api/videos", data={})
assert response.status_code == 400
# Test invalid data
response = admin_client.post("/api/videos", json={"title": ""})
assert response.status_code == 422Tests run automatically on pushes and PRs to both dev and main branches.
PRs and pushes to dev run a faster test suite for quick feedback:
# Skipped tests (slow integration/e2e):
--ignore=tests/test_workflows.py
--ignore=tests/test_worker_integration.py
--ignore=tests/test_transcoder_integration.py
--ignore=tests/test_e2e_upload.py
--ignore=tests/test_transcoder.pyThis runs ~1029 of 1140 tests, skipping the slowest integration tests.
PRs and pushes to main run the complete test suite with coverage reporting:
- All 1140+ tests
- Coverage reports uploaded to Codecov
- Full integration and e2e test coverage
CI workflow (.github/workflows/tests.yml):
- Set up Python virtual environment
- Start PostgreSQL service container
- Install dependencies
- Run tests (quick for dev, full for main)
- Run ruff linting
- Upload coverage report (main only)
# Install dependencies
pip install -e .
pip install pytest pytest-asyncio pytest-cov ruff
# Run linting
ruff check api/ worker/ cli/ tests/ config.py
# Run tests
VLOG_TEST_MODE=1 pytest --cov=api --cov=worker --cov=cli --cov-report=term-missing
# Check coverage meets threshold (80%+)
VLOG_TEST_MODE=1 pytest --cov=api --cov=worker --cov=cli --cov-fail-under=80Tests use a unique PostgreSQL database per test session:
- Each test session generates a unique database name:
vlog_test_<uuid> - Database is created before tests run
- Schema is applied via Alembic migrations
- Database is dropped after tests complete
- Each test gets a fresh database transaction
- Changes are rolled back after each test
- Parallel tests use separate databases
Test fixtures provide:
- Temporary file storage (auto-cleaned)
- Sample categories, videos, workers
- Pre-configured API clients
The following are excluded from coverage:
- Test files (
tests/*) - Database migrations (
migrations/*) __init__.pyfiles- Abstract methods
- Debug code (
if __name__ == "__main__")
See pyproject.toml [tool.coverage.report] for complete list.
# Check PostgreSQL is running
pg_isready -h localhost -p 5432
# Set correct credentials
export VLOG_TEST_PG_USER=vlog
export VLOG_TEST_PG_PASSWORD=vlog_password
# Create test user if needed
psql -c "CREATE USER vlog WITH PASSWORD 'vlog_password';"
psql -c "ALTER USER vlog CREATEDB;"# Install package in development mode
pip install -e .
# Verify installation
python -c "import api; import worker; import cli; print('OK')"# Check conftest.py is loaded
pytest --fixtures tests/
# Verify test database setup
VLOG_TEST_MODE=1 pytest tests/test_database_compatibility.py -v# Skip slow tests during development
pytest -m "not slow"
# Run only fast tests
pytest -m "not slow and not integration"- Test Independence: Each test should be independent and not rely on other tests
- Clean Up: Use fixtures for setup/teardown, don't leave test artifacts
- Descriptive Names: Test names should clearly describe what they test
- Arrange-Act-Assert: Follow AAA pattern for test structure
- Mock External Services: Don't call external APIs in tests
- Test Edge Cases: Test boundary conditions, errors, and unusual inputs
- Keep Tests Fast: Avoid unnecessary delays, use mocks when possible
- Document Intent: Use docstrings to explain what the test verifies
When adding new features:
- Write tests for new functionality
- Update existing tests if behavior changes
- Ensure all tests pass locally
- Check coverage hasn't decreased
- Run linter before committing
See CONTRIBUTING.md for more details on the contribution process.