114 lines
7.2 KiB
Markdown
114 lines
7.2 KiB
Markdown
# 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](operations.md) for starting the instances,
|
|
[peering.md](peering.md), [multitenancy.md](multitenancy.md),
|
|
[architecture.md](architecture.md).
|
|
|
|
## Contents
|
|
|
|
- [The five gates](#the-five-gates)
|
|
- [Postgres gate specifics](#postgres-gate-specifics)
|
|
- [Running everything](#running-everything)
|
|
|
|
## 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; `managedRowsGate` also skips if `psql` is unavailable.
|
|
- e2e drives both instances over a mix of SQLite (`sqlite3`), `curl`, and a
|
|
`saltcorn run-js` shim (`test/sc-exec.js`) that gives the JS body full
|
|
`require()` access where `saltcorn run-js`'s vm sandbox falls short (for
|
|
`Field`, `TableConstraint`, `File`, etc.). It logs in as `admin@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](operations.md) for installing the plugin per tenant before the PG
|
|
gates can pass.
|