testing/cross_backend/session_cap.ts

Cross-backend parity suite for the per-account concurrent-session cap over real HTTP.

The cap is a shipped control on the TS spine (query_session_enforce_limit, DEFAULT_MAX_SESSIONS) that the Rust spine went without — its session INSERT carried no eviction, so every Rust-backed deployment served unlimited concurrent sessions per account. That divergence survived because nothing crossed the wire on it — the TS half is pinned by in-process tests (session_middleware.lifecycle.db.test.ts, session_token_limits.integration.db.test.ts) whose Rust counterparts simply didn't exist to fail. This suite is the pin that runs on *both*, so the next spine to lose the cap fails here rather than in production. Two properties:

  • the cap bounds concurrent sessions, by eviction not refusal — logging in max_sessions + 1 times succeeds every time (a login is never denied for being the one over the line), and the newest cookie resolves afterward.
  • the evicted session is the oldest — the first login's cookie no longer authenticates once the cap is exceeded. A spine with no cap authenticates it fine, which is exactly the defect this catches.

Why max_sessions + 1 logins and not max_sessions. The starting session count isn't zero and isn't guaranteed equal across impls — create_account seeds its own session on the Rust cradle. Overshooting by one guarantees at least one eviction whatever the baseline is, and the *oldest* login is the first to go either way. Asserting on the first and last sessions rather than on a row count keeps the case wire-observable (no DB channel) and baseline-independent.

Both surfaces are flat REST (POST /api/account/login, GET /api/account/status) on every spine, so this is an imperative suite (not a conformance_table row) — the sibling of cookie_attributes.ts / origin.ts / login_security.ts. Cross-process only, and load-bearingly so: each login is held on its own fresh_transport and identified by that transport's cookie jar, which in-process is a jar-less passthrough. That also keeps the suite free of a fourth Set-Cookie parser — reading the raw header is cookie_attributes.ts's job, and this case only needs "does this login still authenticate".

What it does not pin: concurrent creators. Two simultaneous logins can each evict against a stale count under Read Committed and both commit above the cap — see query_session_enforce_limit's race note. That needs a barrier-based test against one backend, not a parity case.

$lib-free by contract (relative specifiers only), like the sibling cross-backend suites.

view source

Declarations
#

2 declarations

describe_session_cap_cross_tests
#

SessionCapCrossTestOptions
#

testing/cross_backend/session_cap.ts view source

SessionCapCrossTestOptions import type {SessionCapCrossTestOptions} from '@fuzdev/fuz_app/testing/cross_backend/session_cap.js';

Options for the session-cap parity suite.

setup_test

Per-test fixture producer (cross-process only — see the module doc).

type SetupTest

readonly

max_sessions?

The cap both spines enforce. Defaults to DEFAULT_MAX_SESSIONS — the TS route default and the Rust const. A consumer that overrides max_sessions on the TS side passes its value here.

type number

readonly

login_path?

REST login route path. Default /api/account/login.

type string

readonly

status_path?

REST account-status route path. Default /api/account/status.

type string

readonly

Depends on
#