Ir al contenido

ADR-009-b — Backend Testing Strategy (Django 5.2)

ADR-009-b — Backend Testing Strategy (Django 5.2)

Sección titulada «ADR-009-b — Backend Testing Strategy (Django 5.2)»

Draft

[[adr-009-testing-strategy|ADR-009]] covers the project-wide testing philosophy; [[adr-009-a-frontend-testing-strategy|ADR-009-a]] covers the frontend. This document decides the Django-specific rules for pytest and DRF.

Three Test Layers. The anti-redundancy rule ([[adr-009-testing-strategy|ADR-009]] §3) applies across all layers.

LayerOwnsLocation
ContractStatus codes + response field shapes, one per API.md endpointtests/test_api_contract.py
Model / DomainFSM transitions, model constraints, computations, servicesapps/<app>/tests/
Edge-case / SecurityThrottling, token blacklist, inactive users, cookie securityapps/<app>/tests/test_*.py
  • New endpoint checklist: (1) contract test, (2) verify domain coverage, (3) add edge-case test if applicable.
  • URL convention: Contract tests use hardcoded paths (/api/v1/employees/), not reverse(). The URL is part of the contract.

Contract Test Rule. No new API.md endpoint may be merged without a corresponding contract test. Each test verifies: happy path, 401 without auth, one error case, paginated envelope fields if applicable.

Fixture Topology.

ScopeLocation
Cross-app (2+ apps)backend/conftest.py
App-local (2+ files in app)apps/<app>/tests/conftest.py
Single-fileInline in test file

Rules: descriptive nouns (active_employee not emp1), composition over standalone factories, autouse limited to cache/throttle resets.

Markers.

MarkerPurpose
@pytest.mark.apiContract tests
@pytest.mark.django_dbDB-hitting tests (via module-level pytestmark)
@pytest.mark.slowTests > 2s
@pytest.mark.regressionBacklog regression guards

Permissions Testing. Deferred until RBAC is implemented. Currently all viewsets use IsAuthenticated only. When RBAC lands: test_<role>_can_<action> + test_<role>_cannot_<action> in apps/<app>/tests/test_permissions.py.

Security Audit Exceptions. Findings from 2026-03-21 audit deliberately excluded:

FindingReason
JWT invalid/expired signatureSimpleJWT handles it; tested indirectly via test_refresh_invalid_token
CORS origin rejectiondjango-cors-headers middleware; no custom code
SQL injectionDjango ORM parameterizes all queries; no raw SQL
RBAC / data isolationMissing feature, not test gap — deferred with permissions testing above
Export leaks password hashAccountExportSerializer whitelists fields explicitly
force_authenticate in FSM testsIntentional — isolates RBAC from cookie auth (documented in module docstring)
  • (+) Each test has exactly one home — no future redundancy.
  • (+) pytest -m api for fast contract feedback; pytest -m regression for backlog guards.
  • (+) Fixture promotion rules keep conftest lean.
  • (-) Contract rollout for ~55 uncovered endpoints is non-trivial.
  • (-) Domain scenarios for hours ledger and coverage rollup documented but not writable until modules exist.