Tests - Homelab Automation Dashboard

Architecture de tests complète pour le backend FastAPI et le frontend JavaScript.

Structure

tests/
├── backend/                    # Tests Python (pytest)
│   ├── conftest.py            # Fixtures partagées (DB, client, mocks)
│   ├── test_routes_auth.py    # Tests authentification JWT
│   ├── test_routes_hosts.py   # Tests CRUD hôtes
│   ├── test_routes_schedules.py # Tests planificateur
│   ├── test_services_ansible.py # Tests service Ansible
│   ├── test_services_scheduler.py # Tests APScheduler
│   ├── test_services_notification.py # Tests ntfy
│   └── test_websocket.py      # Tests WebSocket
├── frontend/                   # Tests JavaScript (Vitest)
│   ├── setup.js               # Configuration (mocks fetch, WS, localStorage)
│   ├── main.test.js           # Tests DashboardManager
│   └── dom.test.js            # Tests rendu DOM
└── README.md                  # Ce fichier

Prérequis

Backend (Python)

pip install pytest pytest-asyncio pytest-cov httpx respx freezegun pytest-mock

Frontend (Node.js)

sudo apt-get install nodejs npm
npm install

Commandes

Exécuter tous les tests

make test-all
# Ou avec coverage:
make test-cov
# Ou séparément:
make test-backend
make test-frontend

Avec couverture

make test-cov

Mode watch (développement)

make test-watch          # Frontend Vitest watch
pytest-watch tests/backend  # Backend (nécessite pytest-watch)

Tests rapides

make test-quick

Tests backend uniquement

pytest tests/backend -v -m "unit"
# Avec coverage:
pytest tests/backend --cov=app --cov-report=html

Tests frontend uniquement

npm test
# Avec coverage:
npm run test:coverage

Marqueurs pytest

Marqueur Description
@pytest.mark.unit Tests unitaires (rapides, isolés)
@pytest.mark.integration Tests d'intégration
@pytest.mark.slow Tests lents (skippés en CI fast)

Exécuter par marqueur:

pytest tests/backend -m "unit"
pytest tests/backend -m "integration"
pytest tests/backend -m "not slow"

Fixtures Principales (Backend)

db_session

Session SQLAlchemy async avec SQLite in-memory. Rollback automatique après chaque test.

client

AsyncClient httpx avec authentification mockée. Dépendances FastAPI overridées.

unauthenticated_client

AsyncClient sans authentification pour tester les erreurs 401.

mock_ansible_service

Mock complet du service Ansible (pas de subprocess réel).

mock_notification_service

Mock du service ntfy (pas de HTTP réel).

host_factory, user_factory, etc.

Factories pour créer des données de test.

async def test_example(db_session, host_factory):
    host = await host_factory.create(db_session, name="test.local")
    assert host.name == "test.local"

Mocks Frontend

setupFetchMock(responses)

Configure les réponses fetch mockées:

setupFetchMock({
  '/api/hosts': [{ id: 'h1', name: 'server1' }],
  '/api/auth/status': { authenticated: true }
});

MockWebSocket

Classe WebSocket mockée avec helpers:

const ws = new MockWebSocket('ws://localhost/ws');
ws._receiveMessage({ type: 'host_updated', data: {...} });

localStorageMock

Mock localStorage avec espions:

expect(localStorageMock.setItem).toHaveBeenCalledWith('accessToken', 'token');

Ajouter un Nouveau Test

Backend

  1. Créer le fichier tests/backend/test_<module>.py
  2. Importer les fixtures depuis conftest.py
  3. Utiliser les marqueurs appropriés
import pytest
from httpx import AsyncClient

pytestmark = [pytest.mark.unit, pytest.mark.asyncio]

class TestMyFeature:
    async def test_something(self, client: AsyncClient, db_session):
        response = await client.get("/api/my-endpoint")
        assert response.status_code == 200

Frontend

  1. Créer le fichier tests/frontend/<name>.test.js
  2. Importer les utilitaires depuis setup.js
import { describe, it, expect } from 'vitest';
import { setupFetchMock } from './setup.js';

describe('MyFeature', () => {
  it('does something', async () => {
    setupFetchMock({ '/api/data': { value: 42 } });
    // Test...
  });
});

Seuils de Couverture

Composant Seuil
Backend global 80%
Services critiques 90%
Frontend global 60%

CI/CD

Les tests s'exécutent automatiquement via GitHub Actions sur:

  • Push sur main ou develop
  • Pull requests vers main ou develop

Voir .github/workflows/tests.yml

Bonnes Pratiques

  1. Isolation: Chaque test est indépendant (DB rollback, mocks reset)
  2. Pas de réseau: Tous les appels HTTP/SSH sont mockés
  3. Déterminisme: Utiliser freezegun pour les tests liés au temps
  4. AAA Pattern: Arrange, Act, Assert clairement séparés
  5. Nommage explicite: test_<action>_<condition>_<expected_result>