Testing and debugging¶
This page is the operational test and triage playbook for the current stack.
When to use this page¶
Use this page when validating local changes, checking runtime health, or diagnosing user-facing failures across API, telemetry, and monitoring.
Audience¶
- Developers debugging service behaviour.
- Operators triaging local or cluster-like environments.
Test commands¶
Backend¶
uv run --project backend ruff check .
uv run --project backend ty check --project backend
uv run --project backend python -m pytest -q
For a faster local loop, add -m "not slow" to skip the boundary-size
runtime tests; CI always runs them.
Backend tests live under backend/tests/, one folder per area: core,
database (schema, migrations, DB sync), gateway, tactic, radar,
proxy, pipelines, simulator and contracts (cross-service, deployment
and documentation contracts). Shared fakes and fixtures live in
backend/tests/support/; test modules never import each other.
Expected result: lint and the type check exit with status 0, and tests
complete without failures. The type check covers the packages listed under
[tool.ty.src] in backend/pyproject.toml; RADAR joins once its lifecycle
attributes are typed.
RADAR production integration¶
The RADAR production integration workflow
runs as listed in Contributing and CI. It builds the
current production backend/Dockerfile, uses Python 3.14 and frozen dependencies,
and selects the TimescaleDB image pinned by the production release manifest.
Its isolated Ubuntu runner initializes Swarm, runs real MQTT/database/task
replacement regressions and cleans labelled fixtures within a 45 minute job.
Production release selection requires a successful main-push run for the same
commit, alongside the other release workflows.
Main also requires this check before merging, bound to GitHub Actions. The dedicated job requires a nonempty JUnit report with zero skipped cases. Missing integration prerequisites therefore fail that job; the general backend suite still supports optional integration skips for local development.
Lanes select their tests by marker, never by file list:
| Marker | Lane | Applied by |
|---|---|---|
postgres |
MQTT collection integration (-m "postgres and not radar_integration") |
Automatically, for every test using disposable_database or member_database |
radar_integration |
RADAR production integration | Module pytestmark |
collection_stack |
MQTT collection integration, broker step | collection_integration/environment.py |
deployment |
Deployment smoke rehearsal | Module pytestmark |
Each lane fails on an empty report or a skipped case
(require_executed_tests.py).
test_suite_layout.py fails when a module reads a lane's environment gate
without carrying that lane's marker, so a new integration test cannot silently
skip everywhere.
To reproduce on a dedicated local Docker engine, initialize Swarm and provide a
disposable PostgreSQL server whose test role can create and drop databases. Set
its host URL in TEST_SCHEMA_DATABASE_URL; tests create uniquely named databases.
The workflow contains the server bootstrap and readiness commands. Then run:
uv sync --project backend --all-packages --all-groups --frozen --python 3.14
export TEST_RADAR_DATABASE_IMAGE="$(python3 .github/scripts/release_image.py postgres)"
export TEST_RADAR_SWARM_IMAGE=off-key-radar-integration:local
export TEST_RADAR_DOCKER=1
docker pull eclipse-mosquitto:2.0
docker build -f backend/Dockerfile -t "$TEST_RADAR_SWARM_IMAGE" \
--build-arg SERVICE_NAME=off-key-mqtt-radar \
--build-arg SERVICE_ROOT=services/mqtt/radar backend
uv run --frozen --project backend python -m pytest -q --tb=short -m radar_integration
The two opt-in variables enable actual Docker lifecycle tests and current-image Swarm tests. These cover reconnect and ACL failures, scoped alarms, signed checkpoint recovery, overload, sensor silence, interrupted creation, concurrent heartbeats/catalogue changes, task shutdown, resource admission and observation time validation. Fixture teardown removes only resources owned by each run.
Frontend¶
Expected result: no lint errors, passing tests, and a successful production build.
CI browser journeys retain one retry for diagnostic evidence. Any test that passes only on retry is classified as flaky and fails CI.
The member-management browser journeys read invitation links from the local mail
sink. Set MAILPIT_BASE_URL to its reachable HTTP endpoint when using custom ports.
The disposable production Swarm rehearsal publishes its SMTP fixture's API on
port 8025 and supplies MAILPIT_BASE_URL=http://127.0.0.1:8025 in smoke.env.
Repository pre-commit gate¶
Runtime health checks¶
# The backend image has no wget or curl; use its Python like the healthchecks do.
probe='import sys, urllib.request as u; print(u.urlopen(sys.argv[1]).read().decode())'
docker compose exec mqtt-proxy /app/bin/python -c "$probe" http://localhost:8010/health
docker compose exec mqtt-proxy /app/bin/python -c "$probe" http://localhost:8010/ready
docker compose exec db-sync /app/bin/python -c "$probe" http://localhost:8009/health
docker compose exec db-sync /app/bin/python -c "$probe" http://localhost:8009/ready/schema
A 503 raises HTTPError instead of printing the body; docker compose ps
shows the same health state.
/ready reports an applied collection revision. An empty selection is healthy idle.
Log inspection¶
Tail critical services:
Inspect recent output for one service:
Debug flows by symptom¶
Frontend loads but charger data is empty¶
- Confirm that
API_PROXY_TARGETis reachable from the Vite dev server; built images proxy/apithrough nginx. - Check Gateway liveness at http://localhost:8000/health.
- Check TACTIC readiness at http://localhost:8001/ready.
- Inspect Gateway and TACTIC logs for the upstream error.
Monitoring start fails¶
-
Confirm that the model catalogue is served:
-
List existing services and look for sensors already claimed:
-
Confirm TACTIC readiness.
- Inspect TACTIC logs for validation, Docker API, image, or network failures.
Telemetry is not arriving¶
- Verify that the source publisher or simulator is active.
- Check the Sources catalog, selected sensors, matching applied revisions and per-broker connection status.
- Check proxy health and readiness.
- Inspect proxy logs for parsing, authentication, or database-write errors.
- Query the Gateway telemetry endpoint.
Evidence or anomalies are missing¶
- Verify that the monitor workload exists.
- Confirm that data reaches its EMQX topic.
- Allow static calibration or adaptive warm-up to complete.
-
Query evidence and anomaly counts:
-
Inspect RADAR logs for model or persistence errors.
Service addresses — local default¶
| Component | Address or port |
|---|---|
| Frontend | 5173 |
| API Gateway | 8000 |
| TACTIC | 8001 |
| DB Sync | Internal 8009 |
| MQTT Proxy health | Internal 8010 |
| PostgreSQL/TimescaleDB | 5432 |
| EMQX MQTT | 1883 |
| Source broker MQTT | 1884, host loopback |
| EMQX dashboard | 18083 |
| Mailpit UI / SMTP | 8025 / 1025 |