Testing
How to run every Metrica test suite locally and how the same suites run in CI.
Tests are the gate on every deploy — the deploy job never runs unless test
passes first (see Deployment).
What “working” means
Section titled “What “working” means”The MVP is verified against a strict definition — one camera stream, detect people, track IDs, count IN/OUT, store events, show a live dashboard. Tests exist to protect that path, nothing more.
Backend tests (pytest, real Postgres)
Section titled “Backend tests (pytest, real Postgres)”The backend uses pytest + pytest-asyncio (asyncio_mode = "auto", from
backend/pyproject.toml). Tests run against a real Postgres, not SQLite —
same engine as local dev and prod.
backend/tests/conftest.py connects to a hardcoded URL:
TEST_URL = postgresql+asyncpg://postgres:postgres@localhost:5433/retailpulse_testKey fixtures:
- an autouse
_schemafixture drops and recreates all tables around every test (usingBase.metadata), so each test starts from a clean schema; - the engine uses
NullPool— pytest-asyncio uses a fresh event loop per test, and pooled asyncpg connections are bound to the loop that created them; clientoverridesget_sessionand the auth signing keys with an in-process ES256 keypair, so tests mint their own valid JWTs (make_token) without hitting Supabase;- fixtures for seeded data (
seed,tunis_seed), auth headers (auth_headers,superadmin_headers) and the worker key header (worker_headers, matchingX-Worker-Key).
Run locally
Section titled “Run locally”You need the test database on localhost:5433. The Docker Postgres container
from local dev serves retailpulse; create the retailpulse_test database once:
docker compose -f backend/docker-compose.yml up -ddocker compose -f backend/docker-compose.yml exec db \ createdb -U postgres retailpulse_testThen:
cd backenduv syncuv run pytest # or: uv run pytest -qWorker tests
Section titled “Worker tests”The CV worker has its own suite:
cd workeruv syncuv run pytestFrontend and admin tests
Section titled “Frontend and admin tests”Both JS apps run their suite through bun:
cd frontend && bun install && bun run testcd admin && bun install && bun run testThe landing and docs sites have no test suite — their workflows are deploy-only.
Tests in CI
Section titled “Tests in CI”Each app’s workflow runs its tests on every push and PR that touches the app directory, gating the deploy job.
| App | Workflow | Test command | Notes |
|---|---|---|---|
| Backend | backend.yml |
uv run pytest -q |
Postgres 16 service on 5433 |
| Dashboard | frontend.yml |
bun run test |
— |
| Admin | admin.yml |
bun run test |
— |
| Landing / Docs | landing.yml / docs.yml |
— | deploy-only, no test gate |
The Postgres service (backend CI)
Section titled “The Postgres service (backend CI)”backend.yml’s test job spins up a postgres:16 service container that
matches conftest.py’s TEST_URL:
services: postgres: image: postgres:16 env: POSTGRES_USER: postgres POSTGRES_PASSWORD: postgres POSTGRES_DB: retailpulse_test ports: - 5433:5432 options: >- --health-cmd "pg_isready -U postgres" --health-interval 10s --health-timeout 5s --health-retries 5env: DATABASE_URL: postgresql+asyncpg://postgres:postgres@localhost:5433/retailpulse_testThe job then runs uv sync and uv run pytest -q. The health-check options make
Actions wait until Postgres is ready before the tests start. DATABASE_URL is
set at the job level because app_settings.database_url is required at import.
Related
Section titled “Related”- Local development — the Docker Postgres container
- Deployment — how the test gate feeds the deploy jobs
- Operations — migrations and monitoring