11 Testing a module locally#
The fixture: examples/module-echo/. A small connected module on
expressModuleRouter (its own package.json with "drop-core-server": "file:../../sdk-server-js", Demo + instore defaults, a README, 11
in-process tests under npm test) that logs every envelope it receives,
answers web with JSON, admin/ping with the member block,
order.validate per its MODE, and declares a form, an action and a card.
Its canonical manifest is package/manifest.json (scope: {modules: {echo: true}, orders: true} — note that orders: {'*': true} would not cover
listing the collection) — the phase-1 test package and the example behind
§2.5.
It is also the test harness's probe:
| Endpoint | What it does |
|---|---|
POST /__mode {mode} |
switches behaviour at runtime, no redeploy: ok | noack | badack | token_fail | reject | sleep5 | down | version:<semver> — a clean install, a missing x-drop-ack, a wrong ack, a failing token stage, a rejecting verify / order.validate, a slow point, a dead endpoint (the module routes 503; the probes stay up), a reported x-drop-module-version |
GET /__log?since= |
the last 200 deliveries with their verified point, label, request and headers — assert what arrived instead of reading server logs |
DELETE /__log |
clears the ring |
GET /__health |
{mode, keys, token, version} — booleans, never the values; token_value is added only when ECHO_EXPOSE_TOKEN=1 and ECHO_ADMIN_KEY are both set (a test build, so the harness can act as the module) |
GET /healthz |
the unguarded liveness ping (Fly's health check) |
The __ probes require the x-echo-key header equal to ECHO_ADMIN_KEY
and are unauthenticated only when it is unset (local runs); the deployed
probe always sets it. The deployed copy runs on Fly.io as the scratch app
drop-module-echo (ams, examples/module-echo/fly.toml) — the one public
HTTPS host an end-to-end install needs, since the gateway only reaches
public https: endpoints and the dev machine has no tunnel. It deploys
from the repo root (fly deploy . --config examples/module-echo/fly.toml):
the Dockerfile copies the four file: packages (core-lib → sdk-js → sdk-server-js → the example) and the root .dockerignore re-includes the
example.
The local loop against the emulator is the exception to "public https:
only": a function running under the Firebase emulator (FUNCTIONS_EMULATOR)
admits a loopback http://127.0.0.1:4310 endpoint at upload, install and
every call (allowLoopback), and the console's upload pre-flight does the
same only with the dev-only VITE_EMULATOR_API override. The platform's own
harness runs that
way first — its emulator config adds the Storage emulator so package objects
never reach a real bucket, sweeps the outbox on demand through
POST /system/module/outbox/sweep, and cleans up through
POST /system/module/registry/delete.
The loop for a module author.
- Write the handlers against
expressModuleRouter(§3.4) and run the echo locally to see the envelope shape;verifyModuleEnvelopeis what your own stack calls if it is not Express. - Write
manifest.json(§2.5);npm run module:packin the starter runs stage 1 locally (§7.2) and writes{id}-{version}.zip. - Deploy the code to a public
https:host (firebase deployinto your own project, or any host) and put its base URL inendpoint. - Hand the zip to a platform admin; they upload it from a store of your
organization on Demo (
instoreis the platform's test store) and install it — the handshake (§4.1) proves your endpoint,verifyruns, and the module page's Health table and Logs tab show every call. - Iterate: code changes need no re-upload while the manifest is unchanged
(
x-drop-module-versiontells the console what you run); a manifest change is a new version → pack → upload → Update.
Platform-side, the harness drives the API matrix by master curl against Demo first and repeats it through the console under Playwright; nothing reaches PROD before that passes end to end.