7.7 KiB
Testing
The saltcorn-idp test suite is a set of five end-to-end "gates", one per
protocol surface. Each gate is a standalone Node script under test/ that
drives a live, already-running Saltcorn instance over the wire (HTTP, LDAPS,
or SAML) and asserts on real responses. There is no mocking: a gate is a black
box client, so a passing gate means the deployed plugin actually behaves.
The gates assume the local dev instances described in
operations.md (MAIN on :3000, TEST on :3001, PG on
:3002). See the README for the plugin overview.
Conventions shared by all gates
- Each gate prints
PASS/FAILper check and ends with aRESULT: <n> passed, <m> failedline. - Exit code:
0when all checks pass (or a gate self-skips),1when any check fails,2on an uncaught error (a thrown exception, e.g. a server that is down and not handled by a self-skip). - The admin credentials used throughout are
admin@local/AdminP@ss1(single-tenant gates) oradmin@<tenant>.local/AdminP@ss1(multi-tenant gates). The relying-party/SP redirect and callback targets are fixed test values (http://localhost:9099/...) and do not need a real listener there -- the gates only inspect the redirect/form, they never follow the callback. - Several gates register their own client/SP/group via the admin UI
(
/admin/idp/...). Each does a delete-then-create at the start, so re-running a gate is safe even if a prior run left an artifact behind.e2e.jsandmtGate.jsalso delete their clients again at the end;samlGate.jsleaves its registered SP in place (the next run's delete-then-create absorbs it).
The five gates
| Gate file | Covers | Prerequisites (instance / port) | Backend | Self-skip when port unreachable | Run command |
|---|---|---|---|---|---|
test/e2e.js |
OIDC: discovery + JWKS on both instances; client registration via admin UI; authorization-code + PKCE + consent; token + id_token RS256 verify; userinfo; groups claim (role:admin + custom group: claim); confidential-client one-time secret |
MAIN :3000 and TEST :3001 both up |
SQLite | No -- throws (exit 2) if an instance is down | node test/e2e.js (or npm test) |
test/ldapGate.js |
LDAP (Phase 4): LDAPS simple bind vs bcrypt hash; authenticated user search (mail, memberOf); wrong-password + anonymous-search rejection; case-insensitive attribute selection; groupOfNames entries; filter-depth rejection; per-message inbound byte cap (DoS); configurable service account (search-then-bind); RFC 4514 DN escaping + uidFromDn unescape (unit) |
MAIN :3000 up AND its LDAPS listener on :1636 |
SQLite | No -- if LDAPS is down the bind checks fail (exit 1) rather than self-skip | node test/ldapGate.js |
test/samlGate.js |
SAML (Phase 5): IdP metadata; SP-initiated SSO (signed assertion + real AuthnStatement); SP registry + ACS allow-list (unregistered SP and out-of-allow-list ACS rejected, 403); DTD/ENTITY (XXE) rejection (400); decompression-bomb rejection (400); empty-ACS SP refused at registration; signed-SP AuthnRequest accepted + unsigned rejected (403) for a cert-registered SP; IdP-initiated SSO (no InResponseTo); Single Logout incl. no-session and wrong-NameID rejection (403) (LAST -- destroys the session) |
MAIN :3000 up |
SQLite | No -- throws (exit 2) if MAIN is down | node test/samlGate.js |
test/mtGate.js |
Multi-tenant OIDC: distinct per-tenant issuers + signing keys; full auth-code + PKCE flow on t1; cross-tenant isolation -- a t1 code, access token, and id_token are all REJECTED at t2 (per-tenant store / Provider / key); positive control that the t1 token still works at t1 |
PG :3002 up, tenants t1 + t2 present with the plugin installed |
Postgres | No explicit skip -- throws (exit 2) if PG/tenants are unavailable | node test/mtGate.js (or npm run test:mt) |
test/ldapMultiTenantGate.js |
Multi-tenant LDAP: tenant-in-DN binding (dc=<tenant>,dc=saltcorn,dc=local); correct-tenant bind + search; same uid under another tenant fails; cross-tenant search denied; unknown tenant denied; tenant-scoped groupOfNames member DNs. Bootstraps each tenant admin over HTTP first |
PG :3002 up AND its LDAPS listener on :1637 |
Postgres | Yes -- if 127.0.0.1:1637 is not reachable it prints SKIP and exits 0 (no-op on SQLite-only setups) |
node test/ldapMultiTenantGate.js |
Expected pass counts
When run against a correctly configured instance, each gate currently reports:
| Gate | Expected passes |
|---|---|
test/e2e.js |
28 |
test/ldapGate.js |
22 |
test/samlGate.js |
28 |
test/mtGate.js |
15 |
test/ldapMultiTenantGate.js |
8 |
A gate that self-skips (only ldapMultiTenantGate.js does this) reports
0 passed, 0 failed (skipped) and exits 0; that is not a failure, but it also
does not contribute its 8 passes.
Ordering and shared state
Checks inside a gate run sequentially and share login/session state -- they are not independent unit tests:
e2e.jsis organized as Phase 0..3 and reuses one admin session and cookie jar across phases. The Phase 1 client registration is a prerequisite for the Phase 1/2 auth-code flows.samlGate.jslogs in once, registers the test SP, then runs SSO checks; the Single Logout check is intentionally LAST because it destroys the session.mtGate.jskeeps a separate cookie jar per tenant and depends on the positivet1flow having produced a token before the cross-tenant rejection checks run.ldapMultiTenantGate.jsbootstraps both tenant admins over HTTP before any LDAP bind.
Do not reorder checks within a gate.
npm scripts
From package.json:
| Script | Command | Gate |
|---|---|---|
npm test |
node test/e2e.js |
OIDC e2e gate (MAIN + TEST, SQLite) |
npm run test:mt |
node test/mtGate.js |
multi-tenant OIDC gate (PG) |
The three remaining gates (ldapGate.js, samlGate.js,
ldapMultiTenantGate.js) have no npm script; run them directly with node as
shown in the table above.
Running the full suite
The PG gates self-skip or fail fast when their backend is unreachable, so which servers you start determines which gates can pass. See operations.md for how to start each instance and install the plugin.
-
SQLite gates (
e2e,ldapGate,samlGate). Start MAIN and TEST:./startServer.sh # MAIN -> :3000 (LDAPS :1636) ./startServerTest.sh # TEST -> :3001 (no LDAP)e2e.jsneeds both MAIN and TEST (Phase 0 checks discovery/JWKS on each).ldapGate.jsneeds MAIN plus its LDAPS listener on:1636(MAIN'senv.shsetsSALTCORN_IDP_LDAP_PORT=1636).samlGate.jsneeds MAIN only.
Then:
npm test # e2e node test/ldapGate.js node test/samlGate.js -
Postgres gates (
mtGate,ldapMultiTenantGate). Start PG and make sure tenantst1andt2exist with the plugin installed per tenant (see operations.md, "PG (multi-tenant): per-tenant install"):./startServerPg.sh # PG -> :3002 (LDAPS :1637)Then:
npm run test:mt # mtGate (HTTP :3002) node test/ldapMultiTenantGate.js # PG LDAPS :1637ldapMultiTenantGate.jsprobes127.0.0.1:1637first and self-skips (exit 0) if the LDAPS listener is not up -- which is also the workaround surface for the intermittent:1637bind flake documented in operations.md.mtGate.jshas no self-skip and will exit 2 if PG or the tenants are not available, so only run it once PG is confirmed up.
If you do not need the multi-tenant coverage, running only the SQLite gates is a valid quick pass.