Running the whole loop
Each mock is useful alone. Together with the Center they close the loop: you send a message from the left, watch it arrive on the right, break the right side, watch the Center cope, and fix it — all on one machine, in a few minutes.
Start everything
From the repo root:
./dev.sh
That brings up the infrastructure (the Supabase stack and NATS) along with the API, the web app and this documentation site. The two mocks are separate — start each in its own terminal:
cd source-test && npm run start:dev # :3003
cd sink-test && npm run start:dev # :3004
What ends up running:
| Port | What |
|---|---|
| 3000 | API — /api/v1, Swagger at /docs |
| 3001 | Web app |
| 3002 | These docs |
| 3003 | Mock source — console at /ui |
| 3004 | Mock sink — console at /ui |
emit will simply fail to connect.Wire the Center to both mocks
Once, in the app:
- Source — an ordinary source of the technology the mock impersonates
(Sigfox for Sigfox mode, The Things Stack for TTN mode — there is no
"mock" technology, see Mock source).
Paste its ingest URL and webhook secret into the mock's connection panel, or
set them in
source-test/.env; confirm withcurl -s localhost:3003/healthand check theingestUrlit reports. Without the secret, expect 401s. - Sink — set your SINK integration's endpoint URL to
http://localhost:3004/sinkand press Test. It should go green. - Routing — open Flow → Routing and wire the source to the sink. Without a route the project broadcasts to every active sink, which works but is worth making explicit.
Open both consoles side by side —
:3003/ui and :3004/ui
— and leave them open.
Loop 1 — the happy path
curl -X POST localhost:3004/control -H 'content-type: application/json' -d '{"mode":"ok"}'
curl -X POST localhost:3003/emit/bidir
What you should see, in order:
- The mock source console records the emission.
- Analytics in the Center:
Received+1, thenDelivered+1. - The mock sink console flashes a new row, labelled
DATA_BIDIR (0/3)and markedawaits reply.
Click the row in the sink console to see exactly what the Center built — this is the fastest way to check an output structure is doing what you meant.
Loop 2 — a dead letter, and its replay
Now break the right side and watch the delivery contract work:
# The sink starts refusing everything
curl -X POST localhost:3004/control -H 'content-type: application/json' \
-d '{"mode":"fail","failStatus":503}'
curl -X POST localhost:3003/emit/bidir
Analytics shows Failed climbing as attempts are retried with backoff. After
eight attempts the delivery lands in Dead letters with its attempt count.
Fix the "outage" and replay:
curl -X POST localhost:3004/control -H 'content-type: application/json' -d '{"mode":"ok"}'
Open Dead letters in the Center and press Replay. The sink console shows it arriving, this time accepted.
That is the whole contract, demonstrated: at-least-once, backoff, dead letter, replay — and replay uses today's configuration, which is why fixing the cause first works.
Loop 3 — the downlink round trip
This is the one that needs both consoles, because neither can answer it alone.
Give the sink an answer to return, then send an uplink that asks for one:
curl -X POST localhost:3004/control -H 'content-type: application/json' \
-d '{"downlinkHex":"deadbeef00000001"}'
curl -X POST localhost:3003/emit/bidir
Read it across the two consoles:
| Where | Tells you |
|---|---|
| Mock sink | The delivery arrived, awaits reply, and what hex went back to the Center |
| Mock source | Whether that downlink actually reached the device — the round-trip panel |
A downlink shown in the sink console was handed to the Center. Only the source console closes the loop. If the sink says it answered and the source says nothing came back, the problem is between them — most often a contract that is not bidirectional or a hex value that is not exactly 16 characters.
Loop 4 — duplicates
The Center delivers at least once, so your real sink must tolerate a repeat. Prove yours does:
curl -X POST localhost:3003/emit/bidir -H 'content-type: application/json' -d '{"seqNumber": 77}'
curl -X POST localhost:3003/emit/bidir -H 'content-type: application/json' -d '{"seqNumber": 77}'
Both carry the same idempotencyKey; the sink console flags the second DUP.
Filter the console by problems to see it among anything else that was not a
clean accept.
Resetting between runs
curl -X DELETE localhost:3003/log # clear the source's emissions
curl -X DELETE localhost:3004/log # clear the sink's deliveries
curl -X POST localhost:3004/control -H 'content-type: application/json' -d '{"mode":"ok"}'
curl -X POST localhost:3003/v2/_reset # only if you have been testing provisioning
What this does and does not prove
It exercises the Center's real ingest, decoding, rules, routing, transform and delivery paths — the same code that runs in production.
It does not prove your actual device transmits correctly, that your real network is configured, or that your real consuming application behaves like the mock. Those are the parts the mocks are standing in for. Treat a green loop here as "the Center is configured correctly", not as "the deployment is done".