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
Context
Sección titulada «Context»[[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.
Decision
Sección titulada «Decision»Three Test Layers. The anti-redundancy rule ([[adr-009-testing-strategy|ADR-009]] §3) applies across all layers.
| Layer | Owns | Location |
|---|---|---|
| Contract | Status codes + response field shapes, one per API.md endpoint | tests/test_api_contract.py |
| Model / Domain | FSM transitions, model constraints, computations, services | apps/<app>/tests/ |
| Edge-case / Security | Throttling, token blacklist, inactive users, cookie security | apps/<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/), notreverse(). 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.
| Scope | Location |
|---|---|
| Cross-app (2+ apps) | backend/conftest.py |
| App-local (2+ files in app) | apps/<app>/tests/conftest.py |
| Single-file | Inline in test file |
Rules: descriptive nouns (active_employee not emp1), composition over standalone factories, autouse limited to cache/throttle resets.
Markers.
| Marker | Purpose |
|---|---|
@pytest.mark.api | Contract tests |
@pytest.mark.django_db | DB-hitting tests (via module-level pytestmark) |
@pytest.mark.slow | Tests > 2s |
@pytest.mark.regression | Backlog 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:
| Finding | Reason |
|---|---|
| JWT invalid/expired signature | SimpleJWT handles it; tested indirectly via test_refresh_invalid_token |
| CORS origin rejection | django-cors-headers middleware; no custom code |
| SQL injection | Django ORM parameterizes all queries; no raw SQL |
| RBAC / data isolation | Missing feature, not test gap — deferred with permissions testing above |
| Export leaks password hash | AccountExportSerializer whitelists fields explicitly |
force_authenticate in FSM tests | Intentional — isolates RBAC from cookie auth (documented in module docstring) |
Consequences
Sección titulada «Consequences»- (+) Each test has exactly one home — no future redundancy.
- (+)
pytest -m apifor fast contract feedback;pytest -m regressionfor 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.