inmake DOCS
Docs / Modules / Testing locally

Testing locally

The echo fixture, the emulator harness and the Demo matrix a module author runs before uploading.

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.

  1. Write the handlers against expressModuleRouter (§3.4) and run the echo locally to see the envelope shape; verifyModuleEnvelope is what your own stack calls if it is not Express.
  2. Write manifest.json (§2.5); npm run module:pack in the starter runs stage 1 locally (§7.2) and writes {id}-{version}.zip.
  3. Deploy the code to a public https: host (firebase deploy into your own project, or any host) and put its base URL in endpoint.
  4. Hand the zip to a platform admin; they upload it from a store of your organization on Demo (instore is the platform's test store) and install it — the handshake (§4.1) proves your endpoint, verify runs, and the module page's Health table and Logs tab show every call.
  5. Iterate: code changes need no re-upload while the manifest is unchanged (x-drop-module-version tells 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.