============================ Utilitaires de Test ============================ Des utilitaires de test basés sur **pytest** sont fournis pour faciliter l'écriture de tests dans un environnement multi-tenant. Module : ``src.testing`` .. module:: src.testing :synopsis: Utilitaires de test pytest pour l'environnement multi-tenant Fixtures pytest — Cycle de vie du tenant ========================================= Module : ``src.testing.testcases`` .. function:: tenant_fixture (fixture, scope="session") Fixture session-scoped qui crée un tenant de test avec son schéma provisionné. Le tenant est réutilisé pour tous les tests de la session puis détruit à la fin. .. function:: tenant_context_fixture (fixture, scope="function") Fixture function-scoped qui active le contexte tenant pour un test. Active le backend d'isolation et positionne les contextvars. .. function:: tenant_tx_context_fixture (fixture, scope="function") Même chose que ``tenant_context_fixture`` mais utilise ``transactional_db`` pour les tests nécessitant des commits réels. .. code-block:: python # conftest.py from src.testing import tenant_fixture, tenant_context_fixture # noqa: F401 # test_invoices.py def test_create_invoice(tenant_context_fixture): # Le contexte tenant est automatiquement actif invoice = Invoice.objects.create( number="INV-001", total=1500.00, ) assert invoice.tenant_id == tenant_context_fixture.id Fixtures pytest — Schéma réutilisable ======================================= Module : ``src.testing.fixtures`` .. function:: tenant_schema (fixture, scope="session") Fixture session-scoped qui crée un tenant de test une seule fois. .. code-block:: python # conftest.py from src.testing.fixtures import tenant_schema # noqa: F401 .. function:: tenant_context (fixture, scope="function") Fixture function-scoped qui active le contexte tenant pour chaque test. .. code-block:: python # conftest.py from src.testing.fixtures import tenant_context # noqa: F401 def test_products(tenant_context): products = Product.objects.all() assert products.count() >= 0 Helpers ======= Module : ``src.testing.helpers`` .. function:: create_test_tenant(slug: str = "test-tenant") -> Tenant Crée un tenant de test avec provisionnement du schéma. .. function:: drop_test_tenant(tenant) -> None Supprime un tenant de test et son schéma. .. function:: create_test_user(email: str = "test@example.com", **kwargs) -> User Crée un utilisateur de test. .. function:: create_test_membership(user, tenant, roles=None, abac_attributes=None) -> TenantMembership Crée une membership de test. Factories ========= Module : ``src.testing.factories`` .. class:: TenantRequestFactory(APIRequestFactory) Factory de requêtes DRF qui injecte automatiquement le header ``X-Tenant-ID``. .. code-block:: python from src.testing.factories import TenantRequestFactory factory = TenantRequestFactory(tenant=my_tenant, user=my_user) request = factory.get("/api/v1/invoices/") # Header X-Tenant-ID automatiquement ajouté .. function:: create_test_policy(**kwargs) -> Policy Crée une politique ABAC de test avec des valeurs par défaut raisonnables. .. function:: evaluate_test_policy(policy, subject_attributes=None, action="read", ...) -> EvaluationResult Évalue une politique unique avec le contexte fourni. Crée le PDP et retourne un ``EvaluationResult``. .. code-block:: python from src.testing.factories import create_test_policy, evaluate_test_policy @pytest.mark.django_db def test_admin_can_delete(): policy = create_test_policy( name="Admin delete", effect="allow", actions=["delete"], resource_type="Invoice", subject_conditions={"field": "roles", "op": "contains", "value": "admin"}, ) result = evaluate_test_policy( policy, action="delete", subject_attributes={"roles": ["admin"]}, ) assert result.decision == "allow"