7.2 KiB
dev-deploy testing
The plugin has five test gates under test/. The e2e suite covers the
single-server SQLite feature surface (MAIN :3000 paired with TEST :3001); the
four Postgres gates cover multi-tenant isolation, mixed-topology push peering,
mixed-topology + tenant<->tenant pull peering, and managed-row data sync against
the multi-tenant instance on :3002.
See also: operations.md for starting the instances, peering.md, multitenancy.md, architecture.md.
Contents
The five gates
| Gate | File | Backend | What it covers | Prerequisites | Run command | Passes |
|---|---|---|---|---|---|---|
| e2e | test/e2e.js |
SQLite (MAIN :3000 + TEST :3001) | Schema + bootstrap identity, mutation capture, pairing, promote/pull, conflict detect/resolve/merge, table constraints, file propagation, file refs in page layout, page_group, workflow_step, config + menu, plugin-config propagation, managed/starter row data, revert, data_mode locks, machine-endpoint HMAC security, admin auth | Both SQLite servers up; plugin installed + bootstrapped; admin@local / AdminP@ss1 on both |
node test/e2e.js (from dev-deploy/; also npm test) |
93 |
| mtGate | test/mtGate.js |
Postgres (:3002) | Phase 0 multi-tenant ISOLATION: each tenant is its own dev-deploy env (distinct env_id), per-tenant Ops/Entities counts, mutating t1 moves only t1's numbers | PG :3002 up; tenants t1 and t2 exist; dev-deploy installed per-tenant |
node test/mtGate.js |
14 |
| mixedTopologyGate | test/mixedTopologyGate.js |
Postgres (:3002) + SQLite MAIN (:3000) | Phase 1 mixed-topology peering: standalone dev (:3000) promotes to tenant t1 on :3002, ops land in t1 not t2; tenant-bound HMAC: a request signed for t1 is rejected (401) at t2, the same request signed for t2 passes auth | MAIN :3000 + PG :3002 up; dev-deploy on MAIN and per-tenant on t1/t2 | node test/mixedTopologyGate.js |
12 |
| managedRowsGate | test/managedRowsGate.js |
Postgres (:3002) + psql |
Phase 2 managed-rows on PG: mark managed adds the hidden _dd_row_uuid column and backfills rows; isolation (t2 gets no _dd_table_modes row); promote t1 -> t2 recreates the table with the SAME row UUIDs |
PG :3002 up; psql reachable; tenants t1/t2 with dev-deploy installed per-tenant |
node test/managedRowsGate.js |
16 |
| pullGate | test/pullGate.js |
Postgres (:3002) + SQLite MAIN (:3000) | Phase 1 PULL peering (reverse of mixedTopologyGate's push): t1 PULLS standalone dev's journal (host-bound HMAC on the GET /dev-deploy/api/journal path); isolation (t2 unchanged by t1's pull); idempotent re-pull ("nothing to pull"); tenant<->tenant pull (t2 pulls t1, dev untouched). apiJournal serves only first-party ops, so a pull relays a node's OWN ops only -- no transitive loops |
MAIN :3000 + PG :3002 up; dev-deploy on MAIN and per-tenant on t1/t2 | node test/pullGate.js |
23 |
Notes:
- The e2e suite runs its tests in order and shares state across them; do not reorder. Assertion failures are caught and counted (one failure does not stop the suite); the runner exits non-zero if any test failed.
- Each Postgres gate self-skips with exit 0 (printing
SKIP) if its required port is not reachable;managedRowsGatealso skips ifpsqlis unavailable. - e2e drives both instances over a mix of SQLite (
sqlite3),curl, and asaltcorn run-jsshim (test/sc-exec.js) that gives the JS body fullrequire()access wheresaltcorn run-js's vm sandbox falls short (forField,TableConstraint,File, etc.). It logs in asadmin@local/AdminP@ss1, scraping the CSRF token from each form's source GET page. - e2e's "plugin mismatch" section assumes both instances carry identical plugin
lists (
base,sbadmin2,dev-deploy) so a promote emits no WARNINGS suffix.
Postgres gate specifics
All four PG gates address tenants by Host header
(<tNN>.localhost.localdomain:3002 selects tenant tNN), keep a separate
cookie jar per tenant, and bootstrap each tenant admin over HTTP
(admin@<tNN>.local / AdminP@ss1, creating the first user if needed).
psql introspection
mtGate, mixedTopologyGate, and pullGate observe state entirely over HTTP by
scraping the dev-deploy dashboard (/admin/dev-deploy/) for the Env ID,
Ops recorded, and Entities tracked rows. managedRowsGate additionally uses
psql because the
hidden _dd_row_uuid column and per-row UUID values are not observable over
HTTP. Its psql connection is fixed in the file:
| Setting | Value |
|---|---|
PGHOST |
/var/run/postgresql |
PGUSER |
scott |
PGDATABASE |
saltcorn_idp |
PGPASSWORD |
peer (dummy; socket peer auth ignores it) |
It queries information_schema.columns for the _dd_row_uuid column and reads
schema-qualified tenant tables (for example "t1"."<tbl>", "t1"._dd_entity_ids,
"t2"._dd_table_modes) to confirm the column add/backfill, per-tenant isolation,
and that promoted rows carry the SAME UUIDs across tenants. It cleans up its
probe table and _dd_* rows in both tenant schemas on exit.
The themeless-tenant sendWrap 500 gotcha
A freshly created tenant has no theme/layout configured, so Saltcorn's own admin
pages that call res.sendWrap (for example GET /table/new/) return HTTP 500.
dev-deploy's admin pages self-render, so the PG gates grab the per-session
_csrf token from a dev-deploy page (GET /admin/dev-deploy/peers) rather than
from /table/new. The actual mutating POST (POST /table,
POST /table/delete/<id>) only mutates and redirects (no sendWrap), so it
succeeds on a themeless tenant and journals the expected op (for example
create_table). This same trick is used to obtain CSRF for dev-deploy's own
admin POSTs (/admin/dev-deploy/peers/add, /promote, /tables/set, etc.).
The gates also use idempotent cleanup so reruns start clean: peer rows are
cleared by env_id before pairing, and managedRowsGate uses a timestamped table
name (mr<Date.now()>) and drops it afterward. pullGate likewise uses
timestamped probe tables (pull_dev_<ts>, pull_t1_<ts>) but drops each ONLY on
its SOURCE instance: a drop is a first-party op too, so dropping on the source
keeps the journal create->drop linear and the next run's pull replays it in order
to clean up the pulled copy. Dropping a PULLED copy instead would author a
competing local op and the next run would legitimately conflict with it.
Running everything
cd ~/claude/saltcorn
./startServer.sh & # MAIN :3000
./startServerTest.sh & # TEST :3001
./startServerPg.sh & # PG multi-tenant :3002
cd dev-deploy
node test/e2e.js # 93
node test/mtGate.js # 14 (PG)
node test/mixedTopologyGate.js # 12 (MAIN + PG)
node test/managedRowsGate.js # 16 (PG + psql)
node test/pullGate.js # 23 (MAIN + PG)
The PG gates self-skip if :3002 (or, for managedRowsGate, psql) is not
available, so on a SQLite-only setup only the e2e suite runs its assertions. See
operations.md for installing the plugin per tenant before the PG
gates can pass.