testing/cross_backend/setup.ts

Per-test fixture protocol shared by in-process and cross-process transports.

Each standard suite body takes a required setup_test: () => Promise<TestFixture> callback and invokes it once per test. The fixture carries everything a test needs to fire requests and assert on a single bootstrapped keeper account — transport, account / actor identity, three header builders, a multi-account mint factory, and (in-process only) the in-memory keyring + raw backend.

default_in_process_setup(options) wraps create_test_app into the SetupTest contract. The cross-process sibling (default_cross_process_setup) lands alongside the spawn-a-backend transport plumbing — it implements the same contract by spawning a binary and bootstrapping over real HTTP.

Declarations
#

18 declarations

view source

BootstrappedBackendHandle
#

testing/cross_backend/setup.ts view source

BootstrappedBackendHandle

Cross-process backend handle enriched with the bootstrapped keeper's captured credentials. Consumers compose this in vitest's globalSetup:

const handle = await spawn_backend(config); const keeper_transport = create_fetch_transport({base_url: config.base_url}); const keeper = await bootstrap({transport: keeper_transport, config}); const bootstrapped: BootstrappedBackendHandle = { ...handle, keeper_transport, keeper_account: keeper.account, keeper_actor: keeper.actor, keeper_cookies: keeper.cookies, };

default_cross_process_setup(bootstrapped, options) reads from this shape — the per-test fixture closes over the keeper credentials so cross-process tests can drive admin-RPC / audit-observer flows against the long-lived bootstrapped admin alongside the per-test signup+login account.

inheritance

extends:

keeper_transport

Transport carrying the keeper session cookie + cookie jar.

readonly

keeper_account

Keeper account JSON captured from POST /bootstrap.

type {readonly id: Uuid; readonly username: string}
readonly

keeper_actor

Keeper actor JSON captured from POST /bootstrap.

type {readonly id: Uuid}
readonly

keeper_cookies

Raw keeper Set-Cookie values — thread into ws_transport for keeper-authenticated WS upgrades.

type ReadonlyArray<string>
readonly

CreateTestAccountOptions
#

testing/cross_backend/setup.ts view source

CreateTestAccountOptions

Options for TestFixture.create_account — mints an additional bootstrapped account alongside the keeper. Matches the existing TestApp.create_account signature so the migration to fixture-style reads is a one-site call rewrite per use.

username

type string
readonly

password_value

type string
readonly

roles

type Array<string>
readonly

CrossProcessSetupOptions
#

testing/cross_backend/setup.ts view source

CrossProcessSetupOptions

extra_keeper_roles

Additional roles to grant the fresh keeper on every per-test reset, *in addition to* the [ROLE_KEEPER, ROLE_ADMIN] defaults the _testing_reset action seeds. Cross-process mirror of in-process extra_keeper_roles on default_in_process_suite_options.

Costs nothing extra per test — the _testing_reset action seeds the keeper in a single transaction regardless of how many roles are in the list.

ROLE_ADMIN is already in the default set, so admin-suite consumers usually pass an empty / omitted array. Consumer-defined roles (e.g. teacher) are passed here when the keeper-acting test needs them.

Keeper ≠ admin. Tests that need a *non-admin* secondary account with ROLE_KEEPER declare it via extra_accounts — ROLE_KEEPER's RoleSpec.grant_paths is bootstrap-only, so it can only be granted at the test-binary bootstrap-equivalent step.

type ReadonlyArray<string>
readonly

extra_accounts

Bootstrap-time secondary accounts seeded alongside the keeper on every per-test reset. See ExtraAccountSpec for why this is a cradle-only bypass. The reset action seeds them in the same transaction as the keeper.

type ReadonlyArray<ExtraAccountSpec>
readonly

default_cross_process_setup
#

testing/cross_backend/setup.ts view source

(handle: ReconstructedBootstrappedBackendHandle, options?: CrossProcessSetupOptions | undefined): SetupTest

Build a SetupTest against a spawned + bootstrapped backend.

Per-test body (unconditional reset — fresh keeper every test):

1. Fire _testing_reset via the keeper's daemon-token channel. The action wipes auth tables, seeds a fresh keeper (with extra_keeper_roles applied), seeds any extra_accounts, and returns the new credentials. 2. Build the TestFixture closing over the new keeper as the fixture's primary account / actor (matching in-process semantics). fixture.extra_accounts[username] exposes any bootstrap-time secondaries. 3. fixture.create_account() mints additional *post-bootstrap* accounts via the production signup + login flow (invite → signup → login → token). Roles go through offer/accept (production consent path).

No reset: boolean opt-in — every test runs against a freshly bootstrapped keeper. This converges in-process and cross-process keeper lifetimes; mutation-cascade tests (password change, revoke-all) and hardcoded-username signup tests work uniformly.

handle

options?

type CrossProcessSetupOptions | undefined
optional

returns

SetupTest

default_in_process_setup
#

testing/cross_backend/setup.ts view source

(options: InProcessSetupOptions): SetupTest

Build a SetupTest that creates a fresh TestApp per call via create_test_app and projects it into the TestFixture shape.

Same factory inputs create_test_app already takes — this helper is a projection layer, not a new lifecycle. fuz_app's own src/test/ and consumer suites pass default_in_process_setup({...factory_inputs}) in place of the old per-suite factory-input bundle. The extra_accounts slot (see InProcessSetupOptions) seeds bootstrap-time secondaries directly via create_test_account_with_credentials against the same DB the keeper just landed on — mirrors the cross-process _testing_reset cradle so suite bodies read fixture.extra_accounts[username] uniformly regardless of transport.

The describe-level auth_integration_truncate_tables / pglite WASM cache lifecycle stays in create_pglite_factory / create_describe_db (testing/db.js) — default_in_process_setup doesn't manage db state beyond what create_test_app already does.

options

returns

SetupTest

default_in_process_suite_options
#

testing/cross_backend/setup.ts view source

<const O extends DefaultInProcessSuiteOptions>(options: O): { setup_test: SetupTest; surface_source: AppSurfaceSpec; capabilities: BackendCapabilities; session_options: O["session_options"]; create_route_specs: O["create_route_specs"]; rpc_endpoints: O["rpc_endpoints"]; }

Build the full in-process suite bundle in a single helper invocation. Output covers {setup_test, surface_source, capabilities} plus every factory input the Tier 1 suites read at their top level (session_options, create_route_specs, rpc_endpoints) — so the call site spreads once and adds only suite-specific extras (roles, skip_routes, input_overrides, db_factories, ...).

// Suite-extras-free call: helper output is the entire options bag. describe_round_trip_validation(default_in_process_suite_options({ session_options, create_route_specs, rpc_endpoints: [rpc_endpoint_spec], })); // With suite-specific extras: spread and add. describe_standard_admin_integration_tests({ ...default_in_process_suite_options({ session_options, create_route_specs, rpc_endpoints, extra_keeper_roles: [ROLE_ADMIN], }), roles, });

Suites that don't read session_options / rpc_endpoints at their top level (round_trip, data_exposure) accept the spread anyway — excess properties on spread sources aren't checked by TS, and the uniform shape keeps consumer call sites mechanical.

options

type O

returns

{ setup_test: SetupTest; surface_source: AppSurfaceSpec; capabilities: BackendCapabilities; session_options: O["session_options"]; create_route_specs: O["create_route_specs"]; rpc_endpoints: O["rpc_endpoints"]; }

DefaultInProcessSuiteOptions
#

testing/cross_backend/setup.ts view source

DefaultInProcessSuiteOptions

Consumer-facing options for default_in_process_suite_options — the minimal factory inputs both default_in_process_setup and create_test_app_surface_spec consume to produce the {setup_test, surface_source, capabilities} bundle.

session_options

type SessionOptions<string>

create_route_specs

type (ctx: AppServerContext) => Array<RouteSpec>

rpc_endpoints

bootstrap

Bootstrap config — top-level slot, single source of truth for both surface generation and live dispatch. Same precedent as rpc_endpoints. Discriminated by mode; omit for the default (no bootstrap route mounted).

app_options

extra_keeper_roles

Additional roles to grant the bootstrapped keeper alongside ROLE_KEEPER — additive, never replaces. The keeper account always holds ROLE_KEEPER (otherwise daemon-token auth breaks); pass extras here for suites that need additional role coverage.

Admin-suite consumers pass [ROLE_ADMIN] so the default keeper can hit admin-gated RPC methods. describe_standard_admin_integration_tests and describe_audit_completeness_tests need this.

type Array<string>

extra_accounts

Bootstrap-time secondary accounts seeded alongside the keeper. See ExtraAccountSpec for the cradle-only-bypass rationale. Same shape as the cross-process extra_accounts option — suites read seeded accounts from fixture.extra_accounts[username] regardless of transport.

type ReadonlyArray<ExtraAccountSpec>

surface_source

Pre-built AppSurfaceSpec — overrides the default which calls create_test_app_surface_spec against the same factory inputs. Pass when surface assembly needs fields outside the shared subset (e.g. env_schema, event_specs, ws_endpoints, transform_middleware).

ExtraAccountFixture
#

testing/cross_backend/setup.ts view source

ExtraAccountFixture

Bootstrap-time-seeded secondary account exposed on the fixture.

account

type {readonly id: Uuid; readonly username: string}
readonly

actor

type {readonly id: Uuid}
readonly

api_token

type string
readonly

session_cookie

type string
readonly

create_session_headers

type (extra?: Record<string, string>) => Record<string, string>
readonly

create_bearer_headers

type (extra?: Record<string, string>) => Record<string, string>
readonly

ExtraAccountSpec
#

testing/cross_backend/setup.ts view source

ExtraAccountSpec

Spec for a bootstrap-time secondary account seeded alongside the keeper by _testing_reset (cross-process) or create_test_app (in-process). Used for accounts whose required roles aren't admin-grantable via offer/accept — primarily ROLE_KEEPER (whose RoleSpec.grant_paths is bootstrap-only), where the only way to land the grant is at the bootstrap-equivalent setup step.

For admin-grantable roles, prefer fixture.create_account({roles}) — that goes through the production offer/accept handlers and observes audit + WS fan-out. extra_accounts is the cradle-only bypass; the runtime has no equivalent action.

username

type string
readonly

password_value

type string
readonly

roles

type ReadonlyArray<string>
readonly

InProcessSetupOptions
#

testing/cross_backend/setup.ts view source

InProcessSetupOptions

Options for default_in_process_setup. Extends CreateTestAppOptions with the same extra_accounts slot the cross-process variant accepts — both transports observe the same bootstrap-time secondary set so suite bodies can read fixture.extra_accounts[username] uniformly.

inheritance

extra_accounts

Additional accounts seeded at this transport's bootstrap-equivalent step. See ExtraAccountSpec for the cradle-only-bypass rationale. Most suites pass undefined / []; the ROLE_KEEPER probe (in describe_standard_admin_integration_tests) is the primary user.

type ReadonlyArray<ExtraAccountSpec>
readonly

reconstruct_bootstrapped_handle
#

testing/cross_backend/setup.ts view source

(serialized: SerializableBootstrappedBackendHandle): ReconstructedBootstrappedBackendHandle

Rebuild a usable handle from the serialized subset. Synthesizes a fresh {@link FetchTransport} primed with the keeper's Set-Cookie values so _testing_reset and other keeper-authenticated calls work. The returned shape omits child and teardown — lifecycle stays with globalSetup; tests that try to teardown themselves wouldn't have a serializable reference anyway.

serialized

returns

ReconstructedBootstrappedBackendHandle

ReconstructedBootstrappedBackendHandle
#

testing/cross_backend/setup.ts view source

ReconstructedBootstrappedBackendHandle

BootstrappedBackendHandle minus the live child / teardown references that only make sense in the globalSetup process. The cross-process provide/inject path strips them on serialization, and the per-test helpers (mint_account, fire_testing_reset, default_cross_process_setup) never read either field — so the test-worker view of the handle has this shape. Also the return type of reconstruct_bootstrapped_handle.

SerializableBootstrappedBackendHandle
#

testing/cross_backend/setup.ts view source

SerializableBootstrappedBackendHandle

Serializable subset of {@link BootstrappedBackendHandle} suitable for vitest's project.provide() — vitest 4 hard-rejects non-serializable values, so the live child: ChildProcess + teardown: () => Promise<void> + keeper_transport: FetchTransport (closure) must stay in the globalSetup process. The handful of fields tests actually read (config, daemon_token, keeper_account, keeper_actor, keeper_cookies) round-trip through structured clone fine.

globalSetup calls {@link serialize_bootstrapped_handle} before project.provide; test files call {@link reconstruct_bootstrapped_handle} on the injected value to rebuild a usable handle (without child / teardown — lifecycle stays with globalSetup).

config

type BackendHandle['config']
readonly

daemon_token

type BackendHandle['daemon_token']
readonly

keeper_account

type BootstrappedBackendHandle['keeper_account']
readonly

keeper_actor

type BootstrappedBackendHandle['keeper_actor']
readonly

keeper_cookies

type ReadonlyArray<string>
readonly

serialize_bootstrapped_handle
#

testing/cross_backend/setup.ts view source

(handle: BootstrappedBackendHandle): SerializableBootstrappedBackendHandle

Strip the non-serializable members so the result can be passed to vitest's project.provide. Call in globalSetup before provide.

handle

returns

SerializableBootstrappedBackendHandle

SetupTest
#

testing/cross_backend/setup.ts view source

SetupTest

Per-test fixture-producing function. Invoked once inside every test() body. The implementation captures factory inputs (in-process) or a long-running backend handle (cross-process) and creates a fresh per-test bundle on each call.

TestAccountFixture
#

TestFixture
#

testing/cross_backend/setup.ts view source

TestFixture

The per-test bundle returned by SetupTest. Every Tier 1 suite body reads exclusively from this shape — no test_app.backend.* reads remain in the suite bodies.

Discriminated by in_process: when true, keyring and backend_internals are present (compile-time narrowed); when false, they're absent and the suite body must either run via the public wire (cross-process) or gate the test with test_if(capabilities.in_process_only, ...). The discriminant value mirrors BackendCapabilities.in_process_only — both source from the same producer (default_in_process_setup vs. cross-process variant).

Suite bodies narrow with assert(fixture.in_process) after setup_test(); sites that reach for keyring or backend_internals are in-process-only by structure and the assertion surfaces a clear failure if a future cross-process consumer reaches them without a test_if gate.

TestFixtureBase
#

testing/cross_backend/setup.ts view source

TestFixtureBase

Fields shared by every TestFixture regardless of transport. The discriminated union below adds in-process-only fields conditionally.

Keeper ≠ admin. fixture.account / fixture.actor refer to the fresh keeper seeded per test. The keeper account holds ROLE_KEEPER + ROLE_ADMIN by default — matching the production bootstrap_account flow. The ROLE_KEEPER role itself does *not* grant admin reach; the bootstrap account just happens to hold both as separate grants. Tests probing the keeper-vs-admin separation (e.g. "non-admin cannot list accounts") declare a secondary at setup-time via extra_accounts: [{username, roles: [ROLE_KEEPER]}] and read it from fixture.extra_accounts[username].

transport

Transport for this test's HTTP requests. Typed as FetchTransport so cross-process tests can call transport.cookies() for WS upgrade cookie threading; in-process provides a no-op cookies() returning [] (in-process tests construct cookies via create_session_headers directly and don't thread WS through this channel).

readonly

fresh_transport

Build a brand-new FetchTransport with an empty cookie jar pinned to the same backend. Use for unauthed assertions (`no cookie on protected route returns 401`, bearer-only calls expected to fall through to the unauthenticated path) where the per-test session cookie carried by transport's jar would otherwise leak into the request and convert a 401 into a 200.

New-per-call, not memoized — each invocation returns a fresh instance. If a call mutates the jar (e.g. an unauthed login attempt returning Set-Cookie) it can't pollute sibling calls.

Pass origin: null for bearer-only probes that must look like non-browser callers — the auth middleware silently discards bearer credentials when Origin/Referer is present, so a default Origin: <base_url> would convert "bearer + no Origin → 200" into "bearer + Origin → discarded → 401" cross-process. In-process the wrapper is stateless and the option is a no-op (no auto-Origin to suppress).

In-process this is functionally identical to transport (the wrapper's cookies(): [] is a no-op already); cross-process the returned transport starts with an empty jar at the same base_url.

type (options?: {readonly origin?: string | null}) => FetchTransport
readonly

account

The freshly-bootstrapped keeper account.

type {readonly id: Uuid; readonly username: string}
readonly

actor

The actor linked to the keeper account.

type {readonly id: Uuid}
readonly

create_session_headers

Build request headers with the keeper's session cookie.

type (extra?: Record<string, string>) => Record<string, string>
readonly

create_bearer_headers

Build request headers with the keeper's bearer token.

type (extra?: Record<string, string>) => Record<string, string>
readonly

create_daemon_token_headers

Build request headers with the daemon token (keeper auth).

type (extra?: Record<string, string>) => Record<string, string>
readonly

create_account

Mint an additional bootstrapped account for cross-account / multi-user tests. In-process: re-uses create_test_account_with_credentials against the same DB; cross-process: goes through the consumer-supplied DB-admin channel.

type (options?: CreateTestAccountOptions) => Promise<TestAccountFixture>
readonly

extra_accounts

Bootstrap-time-seeded secondaries, keyed by their declared username. Populated from the extra_accounts option passed to default_in_process_setup / default_cross_process_setup. Empty for suites that don't declare any.

type Readonly<Record<string, ExtraAccountFixture>>
readonly

Depends on
#