Skip to content

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 configuration

Running Tests ​

Prerequisites ​

bash
cd backend
pip install -r requirements.txt

Run All Tests ​

bash
# 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 -v

Run Specific Test File ​

bash
USE_MOCK_PROXMOX=1 python -m pytest apps/backups/test_api.py

Run Specific Test Class ​

bash
USE_MOCK_PROXMOX=1 python -m pytest tests/test_models.py::TestUserModel

Run Specific Test ​

bash
USE_MOCK_PROXMOX=1 python -m pytest tests/test_models.py::TestUserModel::test_create_user

Run with Debugging ​

bash
USE_MOCK_PROXMOX=1 python -m pytest -xvs apps/backups/test_api.py::TestBackupAPIEndpoints::test_create_backup_success

Options:

  • -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:

python
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_active

Coverage:

  • 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:

python
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:

python
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']) == 1

Coverage:

  • Application CRUD operations
  • Backup management endpoints
  • Catalog browsing
  • Authentication and permissions

4. Task Tests (apps/*/test_tasks.py) ​

Test Celery task execution:

python
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 True

Coverage:

  • Asynchronous task execution
  • Error handling in tasks
  • Task retries and failures

5. Integration Tests ​

Test complete workflows:

python
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:

python
@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 client

Test Database ​

Tests use an in-memory SQLite database:

python
# 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 ​

python
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 here

Mock Celery Tasks ​

python
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 == 202

Environment Variables ​

Use USE_MOCK_PROXMOX=1 to load mock Proxmox service:

python
# 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 ProxmoxService

Writing New Tests ​

Test Structure ​

python
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 functions
  • Test prefix for test classes
  • Descriptive names: test_create_backup_with_invalid_compression
  • Follow AAA pattern: Arrange, Act, Assert

Assertions ​

python
# 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:

bash
USE_MOCK_PROXMOX=1 python -m pytest --cov=apps --cov=tests \
  --cov-report=html --cov-report=term

Current 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 ​

yaml
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 pytest

Debugging Failed Tests ​

python
def test_with_debugging(sample_user):
    print(f"User: {sample_user}")
    print(f"User email: {sample_user.email}")

Run with -s flag:

bash
USE_MOCK_PROXMOX=1 python -m pytest tests/test_models.py -s

Interactive Debugging ​

bash
USE_MOCK_PROXMOX=1 python -m pytest tests/test_models.py --pdb

This drops into pdb debugger on test failure.

Inspect Database State ​

python
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 inspection

Performance Testing ​

Measure Test Execution Time ​

bash
USE_MOCK_PROXMOX=1 python -m pytest --durations=10

Shows slowest 10 tests.

Profile Specific Test ​

bash
USE_MOCK_PROXMOX=1 python -m pytest tests/test_models.py \
  --profile --profile-svg

Common 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 ​

  1. Use fixtures for common setup
  2. Mock external services (Proxmox, etc.)
  3. Test one thing per test function
  4. Use descriptive names for test functions
  5. Follow AAA pattern (Arrange, Act, Assert)
  6. Keep tests fast (use in-memory DB)
  7. Test edge cases not just happy path
  8. Document non-obvious tests with comments

Test Version: 1.0 Last Updated: October 31, 2025 Test Status: ✅ 102/102 Passing (100%)

Released under the MIT License.