Testing Guide
Overview
Proximity has comprehensive test coverage with 102/102 tests passing (100% coverage) for the backend.
Test Structure
backend/
├── tests/ # Shared tests
│ ├── test_models.py # Model tests
│ ├── test_services.py # Service tests
│ ├── test_schemas.py # Authentication tests
│ ├── test_utils.py # Utility function tests
│ └── conftest.py # Shared fixtures
├── apps/
│ ├── applications/
│ │ └── test_node_selection.py
│ ├── backups/
│ │ ├── test_api.py # API endpoint tests
│ │ ├── test_tasks.py # Celery task tests
│ │ └── conftest.py # Backup fixtures
│ ├── catalog/
│ │ └── test_api.py # Catalog endpoint tests
│ └── [other apps]/
└── conftest.py # Root pytest configurationRunning Tests
Prerequisites
cd backend
pip install -r requirements.txtRun All Tests
# With mock Proxmox service
USE_MOCK_PROXMOX=1 python -m pytest
# With coverage report
USE_MOCK_PROXMOX=1 python -m pytest --cov=apps --cov=tests
# Verbose output
USE_MOCK_PROXMOX=1 python -m pytest -vRun Specific Test File
USE_MOCK_PROXMOX=1 python -m pytest apps/backups/test_api.pyRun Specific Test Class
USE_MOCK_PROXMOX=1 python -m pytest tests/test_models.py::TestUserModelRun Specific Test
USE_MOCK_PROXMOX=1 python -m pytest tests/test_models.py::TestUserModel::test_create_userRun with Debugging
USE_MOCK_PROXMOX=1 python -m pytest -xvs apps/backups/test_api.py::TestBackupAPIEndpoints::test_create_backup_successOptions:
-x- Stop on first failure-v- Verbose output-s- Show print statements--pdb- Drop into debugger on failure-k- Filter by test name pattern
Test Categories
1. Model Tests (tests/test_models.py)
Test Django model functionality:
def test_create_user():
"""Test user creation"""
user = User.objects.create_user(
username='testuser',
email='test@example.com',
password='testpass123'
)
assert user.username == 'testuser'
assert user.is_activeCoverage:
- User creation and properties
- Proxmox host and node models
- Application model lifecycle
- Backup model operations
- System settings
2. Service Tests (tests/test_services.py)
Test business logic services:
def test_allocate_ports_success():
"""Test port allocation service"""
ports = PortManagerService.allocate_ports(app_id, count=3)
assert len(ports) == 3
assert all(8000 <= p < 9000 for p in ports)Coverage:
- Port manager service
- Catalog service
- Proxmox service integration
3. API Tests (apps/*/test_api.py)
Test REST endpoints:
def test_list_applications(auth_client, sample_application):
"""Test GET /api/apps/"""
response = auth_client.get('/api/apps/')
assert response.status_code == 200
data = response.json()
assert len(data['applications']) == 1Coverage:
- Application CRUD operations
- Backup management endpoints
- Catalog browsing
- Authentication and permissions
4. Task Tests (apps/*/test_tasks.py)
Test Celery task execution:
def test_create_backup_success(sample_application, sample_backup):
"""Test backup creation task"""
with patch('apps.backups.tasks.ProxmoxService') as MockProxmox:
result = create_backup_task(application_id=sample_application.id)
assert result['success'] is TrueCoverage:
- Asynchronous task execution
- Error handling in tasks
- Task retries and failures
5. Integration Tests
Test complete workflows:
def test_deploy_and_backup_workflow():
"""Test full deployment and backup workflow"""
# 1. Deploy application
app = deploy_application('nginx')
assert app.status == 'running'
# 2. Create backup
backup = create_backup(app)
assert backup.status == 'completed'
# 3. Restore from backup
restored = restore_backup(app, backup)
assert restored.status == 'running'Test Fixtures
Common fixtures defined in conftest.py:
@pytest.fixture
def sample_user():
"""Create a test user"""
return User.objects.get_or_create(
username='testuser',
defaults={'email': 'test@example.com'}
)[0]
@pytest.fixture
def sample_application(sample_user):
"""Create a test application"""
return Application.objects.create(
id=f'test-app-{uuid.uuid4()}',
catalog_id='nginx',
owner=sample_user,
hostname='test-app.local',
lxc_id=random.randint(100, 9999)
)
@pytest.fixture
def auth_client(client, sample_user):
"""Create authenticated test client"""
client.force_login(sample_user)
return clientTest Database
Tests use an in-memory SQLite database:
# conftest.py
@pytest.fixture(scope='session')
def django_db_setup(django_db_blocker):
settings.DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': ':memory:',
}
}
with django_db_blocker.unblock():
call_command('migrate', '--run-syncdb', verbosity=0)Benefits:
- Fast test execution
- No database cleanup needed
- Isolated per test session
- In-memory operations
Mocking Strategy
Mock Proxmox Service
from unittest.mock import patch, MagicMock
def test_deploy_with_mocked_proxmox():
with patch('apps.proxmox.services.ProxmoxService') as MockProxmox:
mock_service = MockProxmox.return_value
mock_service.deploy_container.return_value = {
'status': 'created',
'lxc_id': 101
}
# Your test code hereMock Celery Tasks
def test_with_celery_mocked():
with patch('apps.backups.tasks.create_backup_task.delay'):
# Celery tasks won't actually execute
response = client.post('/api/apps/1/backups/')
assert response.status_code == 202Environment Variables
Use USE_MOCK_PROXMOX=1 to load mock Proxmox service:
# apps/proxmox/__init__.py
USE_MOCK = os.getenv('USE_MOCK_PROXMOX') == '1'
if USE_MOCK:
from .mock_service import MockProxmoxService as ProxmoxService
else:
from .services import ProxmoxServiceWriting New Tests
Test Structure
import pytest
from django.test import Client
@pytest.mark.django_db
class TestMyFeature:
"""Test suite for my feature"""
def test_happy_path(self, sample_user, auth_client):
"""Test the happy path"""
# Arrange
expected = "expected_value"
# Act
result = do_something()
# Assert
assert result == expected
def test_error_case(self, sample_user, auth_client):
"""Test error handling"""
with pytest.raises(ValueError):
do_something_invalid()Naming Conventions
test_prefix for test functionsTestprefix for test classes- Descriptive names:
test_create_backup_with_invalid_compression - Follow AAA pattern: Arrange, Act, Assert
Assertions
# Simple assertions
assert user.is_active
# Comparison assertions
assert response.status_code == 200
# Membership assertions
assert 'error' in response.json()
# Exception assertions
with pytest.raises(ValueError):
invalid_operation()
# Django assertions
from django.test import TestCase
assert User.objects.filter(username='test').exists()Coverage Report
Generate coverage report:
USE_MOCK_PROXMOX=1 python -m pytest --cov=apps --cov=tests \
--cov-report=html --cov-report=termCurrent Coverage:
apps/applications - 95%
apps/backups - 98%
apps/catalog - 99%
apps/core - 85%
apps/proxmox - 80%
tests/ - 100%
Overall: 94%Continuous Integration
Tests run automatically on:
- Pull requests
- Commits to main branch
- Scheduled nightly runs
GitHub Actions Example
name: Backend Tests
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
services:
postgres:
image: postgres:15
options: >-
--health-cmd pg_isready
--health-interval 10s
steps:
- uses: actions/checkout@v2
- uses: actions/setup-python@v2
with:
python-version: '3.12'
- run: |
cd backend
pip install -r requirements.txt
USE_MOCK_PROXMOX=1 python -m pytestDebugging Failed Tests
Print Debugging
def test_with_debugging(sample_user):
print(f"User: {sample_user}")
print(f"User email: {sample_user.email}")Run with -s flag:
USE_MOCK_PROXMOX=1 python -m pytest tests/test_models.py -sInteractive Debugging
USE_MOCK_PROXMOX=1 python -m pytest tests/test_models.py --pdbThis drops into pdb debugger on test failure.
Inspect Database State
def test_with_db_inspection(db):
user = User.objects.create_user(username='test')
print(User.objects.filter(username='test').values())
# Database will still exist after test for inspectionPerformance Testing
Measure Test Execution Time
USE_MOCK_PROXMOX=1 python -m pytest --durations=10Shows slowest 10 tests.
Profile Specific Test
USE_MOCK_PROXMOX=1 python -m pytest tests/test_models.py \
--profile --profile-svgCommon Issues
Issue: Import Errors in Tests
Solution: Ensure USE_MOCK_PROXMOX=1 is set before imports.
Issue: Database Constraint Errors
Solution: Use get_or_create() pattern in fixtures to avoid unique violations.
Issue: Test Isolation Failures
Solution: Generate unique IDs using UUID for test data.
Issue: Async Task Tests Failing
Solution: Mock Celery tasks with @patch.object().
Best Practices
- Use fixtures for common setup
- Mock external services (Proxmox, etc.)
- Test one thing per test function
- Use descriptive names for test functions
- Follow AAA pattern (Arrange, Act, Assert)
- Keep tests fast (use in-memory DB)
- Test edge cases not just happy path
- Document non-obvious tests with comments
Test Version: 1.0 Last Updated: October 31, 2025 Test Status: ✅ 102/102 Passing (100%)