Testing
The suite is pytest + pytest-django. Settings: EMAW_BACKEND.settings.test (development minus external services: locmem cache and email, eager Celery, no Redis needed).
One-time: a local PostgreSQL the suite may own
Tests create and drop databases, which the shared dev role cannot do. Run a throwaway container once:
docker run -d --name emaw_test_pg `
-e POSTGRES_USER=emaw_test -e POSTGRES_PASSWORD=emaw_test -e POSTGRES_DB=emaw_test `
-p 127.0.0.1:5434:5432 postgres:16-alpine
Start it again later with docker start emaw_test_pg.
Run
The TEST_DB_* variables replace the matching DB_* for the test process only. From Git Bash:
TEST_DB_HOST=127.0.0.1 TEST_DB_PORT=5434 TEST_DB_USER=emaw_test TEST_DB_PASSWORD=emaw_test TEST_DB_NAME=emaw_test \
.venv/Scripts/python.exe -m pytest
From PowerShell:
$env:TEST_DB_HOST="127.0.0.1"; $env:TEST_DB_PORT="5434"; $env:TEST_DB_USER="emaw_test"; $env:TEST_DB_PASSWORD="emaw_test"; $env:TEST_DB_NAME="emaw_test"
.venv\Scripts\python.exe -m pytest
Useful variants:
python -m pytest crm -k mailable # one app, one keyword
python -m pytest --create-db # after schema changes (--reuse-db is on by default)
python -m pytest -x -q # stop at first failure
Layout
| Item | Where |
|---|---|
| Config | pytest.ini: testpaths = crm campaigns EMAW events resources store donations payments shipping deposco notes bibles ai, addopts = --reuse-db -q, python_files = test_*.py |
| Shared fixtures | root conftest.py: plain_user, superuser, crm_staff_user, contact, api_client, auth_client (issues a real SimpleJWT bearer token), and an autouse fixture that patches Stripe card/subscription/invoice lookups |
| Factories | <app>/tests/factories.py (factory-boy); HTTP mocking with responses |
| Tests | <app>/tests/test_*.py |
Every ticket ships tests for its own code in its app's tests/ folder.
Coverage today
| App | Test files |
|---|---|
| crm | 31 |
| store | 15 |
| campaigns | 12 |
| EMAW | 10 |
| events | 9 |
| deposco | 6 |
| donations | 5 |
| resources | 4 |
| shipping | 3 |
| payments | 2 |
| notes, bibles, ai | 0 (in testpaths, nothing written yet) |
The three empty apps are on the known gaps list.
Writing a test
import pytest
from crm.tests.factories import ContactFactory
@pytest.mark.django_db
def test_contact_list_requires_capability(auth_client, plain_user):
client = auth_client(plain_user)
response = client.get("/rest/crm/contacts/")
assert response.status_code == 403
@pytest.mark.django_db
def test_staff_sees_contacts(auth_client, crm_staff_user):
ContactFactory(first_name="Ada")
client = auth_client(crm_staff_user)
response = client.get("/rest/crm/contacts/")
assert response.status_code == 200
assert response.json()["count"] == 1
Rules of thumb:
- Hit endpoints through
auth_client, not view functions, so permissions and throttles are exercised. - Mock Stripe, Deposco, ACS, Graph, OpenAI and Azure Maps at the HTTP boundary (
responses) or at the client module; never let a test reach the network. - Celery is eager in test settings, so tasks run inline; assert on their effects.
Root scratch scripts
verify_redis_down.py, verify_redis_opt.py and verify_seed_dupes.py in the repo root use settings/local_verify.py and a committed verify_db.sqlite3. They are manual checks, not part of the suite.