Repository navigation
Testing
Two suites guard the Hub, and they are honest about what each is for: the integration suite proves the domain logic against a live server and database, and the end-to-end suite proves the browser experience. Neither mocks the access engine — the point of the tests is that permission rules, transitions and concurrency behave correctly against the real thing.
npm run test:int # vitest — needs dev server + migrated DB on :3000
npm run test:e2e # playwright — browser flows against the same
npm run test # bothBoth provision their own accounts and content per run, which is why they can safely run against a dev database — they build what they need rather than expecting fixtures. Keep npm run dev up while they run.
The specs live under tests/ and are organised by domain — auth, decisions, elections, membership, negotiations, notifications, and the authority model. The authority spec (authority-records.int.spec.ts) is the best example of what "real" testing looks like here:
- derivation: a record resolves to capabilities through
deriveAuthority; - the write path:
grantAuthority/revokeAuthoritytransition rows correctly; - substitute confinement: a substitute's grant can't exceed the seat it covers;
-
the advisory lock: concurrent grant attempts serialise through
pg_locks, verified by inspecting the lock state itself; - the unique index: two racing grants of the same record produce one row, not two;
- provenance:
migrated_fromis write-once.
That last cluster is the interesting bit — the tests exercise the concurrency machinery, not just the happy path, because the bugs that matter live in the gaps between requests.
Every PR runs the matrix in .github/workflows/verify.yml:
flowchart LR
PR --> Lint[Prettier + ESLint]
PR --> Build[Build + types-drift check]
PR --> Provision["Provision<br/>(fresh migration chain)"]
PR --> Node22[Node 22 suite]
PR --> Node24[Node 24 suite]
PR --> MCP["Content MCP smoke<br/>(handshake → 18 tools)"]
PR --> Deploy[Vercel preview]
The provision job deserves its own mention: it builds the entire migration chain on an empty database, so a broken migration fails CI rather than a deploy. The MCP smoke job boots mcp-content standalone and proves the agent surface still answers initialize and lists its tools — the contract the whole agent workflow depends on.
-
The login rate limiter. Several test runs in quick succession will exhaust the 20-attempts-per-15-minutes budget and every subsequent login 429s. Restart
npm run dev— the limiter is in-memory. -
pagination: falsestill returns{ docs }. Payload's find returns aPaginatedDocsshape either way;pagination: falsejust returns all of them. Tests asserting on the raw return value trip on this. -
Migrations can't run inside a transaction.
DROP DATABASE,CREATE DATABASEand friends fail in aBEGIN…COMMITblock — run them standalone. - The dev DB is stateful. A spec that asserts "no active records" will fail if earlier runs left some; assert the transition, not the emptiness.
Put it in the domain's spec file (or a new *.int.spec.ts if the domain is new). Provision what you need through the same endpoints a member would use — don't seed rows directly, because then the test proves the rows exist, not that the system creates them. And when the feature touches authority, assert on the derived effect (can the account do the thing), not just the row.