Phase 10 — Smoke tests
You have a running stack. Before you hand it to operators or wire up DNS for real charge points, prove it works. This phase walks through the smoke tests that catch the failure modes we have actually seen in the field: a database that responds but is missing migrations, a SteVe instance the web app cannot reach, a Lago tenant that rejects the API key, and sync jobs that hang.
By the end of this phase you will have a green health endpoint, a working admin login, and confirmation that OCPP traffic flows from a real or simulated ChargeBox through to the web UI.
Prerequisites
Section titled “Prerequisites”- Phases 1–9 complete. The web app, SteVe, Postgres, Lago, and the Email Worker are all running.
- DNS resolves (or you have
/etc/hostsoverrides) for the web app and SteVe public hostnames. - You have one administrator account that can sign in via magic link.
- You have access to either a real ChargeBox you can point at SteVe,
or
ocpp-go/steve-clito simulate one.
-
Hit the health endpoint
The health endpoint is unauthenticated and returns a JSON document describing each subsystem. From the host running the web container:
Terminal window curl -sS https://app.example.com/api/health | jqA healthy deployment returns HTTP 200 and:
{"status": "ok","timestamp": "2025-01-21T12:00:00.000Z","checks": {"database": { "status": "ok", "latencyMs": 3 },"steve": { "status": "ok" },"lago": { "status": "ok" },"sync": { "status": "ok", "lastRun": "2025-01-21T11:58:00.000Z" }}}The endpoint is defined in
web/routes/api/health.ts. It returns HTTP 503 if any check isunhealthy, and HTTP 200 with"status": "degraded"if a check is degraded (for example, SteVe or Lago not configured). -
Interpret each check
Read the response against the table below. Each key under
checksmaps to a specific subsystem.Check What it tests okdegradedunhealthydatabaseSELECT 1against the configured PostgresQuery succeeds — Connection refused, auth failure steveSTEVE_API_URLandSTEVE_API_KEYboth setBoth configured One or both missing — lagoLAGO_API_URLandLAGO_API_KEYboth setBoth configured One or both missing — syncNo sync run has been in runningstate for more than 30 minLast run completed cleanly One or more sync runs stuck > 30 min Query against sync_runsfailed -
Verify SteVe reachability from the web container
Open a shell inside the web container and call the SteVe API directly:
Terminal window docker compose exec web sh -c \'curl -sS -u "$STEVE_API_USER:$STEVE_API_KEY" "$STEVE_API_URL/api/v1/chargepoints" | head'You should get a JSON array (possibly empty
[]). If you get an HTML login page, the API user is not configured in SteVe. If you get a connection error, check that the web container can resolve and reach the SteVe service name on the Docker network. -
Verify Lago reachability
From the same shell:
Terminal window docker compose exec web sh -c \'curl -sS -H "Authorization: Bearer $LAGO_API_KEY" \"$LAGO_API_URL/api/v1/organizations" | jq .organization.name'You should see your Lago organization name. A
401means the API key is wrong. A connection error means the web container cannot reach Lago. -
Sign in via magic link
-
Open
https://app.example.comin a browser. -
Enter the email address of the bootstrapped admin user.
-
Watch the Email Worker logs:
Terminal window docker compose logs -f email-workerYou should see the magic link message being processed. If the message is delivered to a real inbox, open it. If you are running in dev with the email worker configured to log instead of send, copy the link from the logs.
-
Open the magic link. You should land on the admin console signed in as the admin user.
If the magic link email never arrives, see Email worker troubleshooting — the most common cause is an unverified sender in your transactional email provider.
-
-
Drive an OCPP transaction
This is the end-to-end test that exercises the whole stack: SteVe accepts an OCPP
BootNotification, the sync job picks it up, and the ChargeBox appears in the admin console.Point the charge point’s OCPP URL at:
wss://ocpp.example.com/steve/websocket/CentralSystemService/<chargeBoxId>Power-cycle the charge point. Within 30 seconds, the ChargeBox should appear in Admin → ChargeBoxes with a green online dot.
Use
ocpp-goor any OCPP 1.6-J simulator:Terminal window ocpp-cli --url wss://ocpp.example.com/steve/websocket/CentralSystemService \--id SMOKE-TEST-01 \boot --vendor TestVendor --model TestModelThe simulator should receive an
Acceptedboot response. Refresh Admin → ChargeBoxes;SMOKE-TEST-01should appear. -
Trigger a sync run and confirm it completes
From the admin console, navigate to Admin → Sync and trigger a manual sync. Refresh the health endpoint:
Terminal window curl -sS https://app.example.com/api/health | jq .checks.synclastRunshould reflect the timestamp you just triggered, andstatusshould beok. Ifstatusisdegradedwith the messageN sync(s) running > 30 minutes, you have a stuck sync run — see Sync runbook for the recovery procedure.
Configure environment variables
Section titled “Configure environment variables”No new env vars are introduced in this phase. The variables that affect the health endpoint’s output are:
| Name | Source | Required for ok |
|---|---|---|
DATABASE_URL | web/.env | yes |
STEVE_API_URL | web/.env | yes (else degraded) |
STEVE_API_KEY | web/.env | yes (else degraded) |
LAGO_API_URL | web/.env | yes (else degraded) |
LAGO_API_KEY | web/.env | yes (else degraded) |
If any of the SteVe or Lago variables are unset, the health endpoint
returns HTTP 200 with status: "degraded" — useful in lower
environments where you may not have a Lago tenant yet, but not
acceptable in production.
Verify
Section titled “Verify”A deployment passes smoke tests when all of the following are true:
curl https://app.example.com/api/healthreturns HTTP 200 with"status": "ok".- Every entry under
checksis"status": "ok". - An admin can sign in via magic link.
- A real or simulated ChargeBox appears in the admin console after a
BootNotification. - A manual sync run completes and updates
checks.sync.lastRun.
If something goes wrong
Section titled “If something goes wrong”database is unhealthy. The web app cannot reach Postgres.
Check DATABASE_URL, network reachability from the web container,
and Postgres logs. If the connection works but the query fails,
migrations may not have been applied — re-run Phase 4.
steve or lago is degraded but you set the env vars.
Confirm the env vars are visible to the web container, not just the
host shell:
docker compose exec web env | grep -E 'STEVE|LAGO'Restart web after changing .env.
sync is degraded with stuck runs. A previous sync crashed
without releasing its lock. The health check flags runs in running
state older than 30 minutes.
Magic link email never arrives. Check email-worker logs. The
two common causes are an unverified sender address with your
transactional email provider, or the worker not having credentials to
pull from the outbound queue.
ChargeBox connects to SteVe but does not appear in the admin console. SteVe accepted the connection but the web app has not synced it yet. Trigger a manual sync. If the ChargeBox still does not appear, confirm the SteVe API credentials used by the web app have read access to charge points.
Next phase
Section titled “Next phase”Smoke tests passing means the stack is functionally complete. From here you should:
- Configure backups — see Backups runbook.
- Set up monitoring against
/api/health— see Monitoring. - Plan your first upgrade — see Upgrades runbook.