testing/cross_backend

10 modules

  • testing/cross_backend/backend_config.ts

    Cross-process backend configuration.

    BackendConfig describes a spawnable test binary — argv, mount paths, env vars, bootstrap credentials, daemon-token discovery path, declared capabilities. Consumer projects ship per-backend factories (deno_backend_config(), rust_backend_config(), spine_stub_backend_config()) that produce this shape; spawn_backend consumes it.

    fuz_app ships spine_stub_backend_config() as a convenience preset (operational dep on testing_spine_stub — path-based discovery, no package.json coupling to private_fuz). Otherwise backend-specific knowledge (binary paths, port choices, env vars) is a consumer concern; fuz_app's testing library knows nothing about Deno, Cargo, or any specific runtime beyond that preset.

  • testing/cross_backend/bootstrap_backend.ts

    One-call spawn + bootstrap helper.

    Composes spawn_backend(config) and bootstrap({transport, config}) so a consumer's vitest globalSetup reduces to a single await:

    import {bootstrap_backend} from '@fuzdev/fuz_app/testing/cross_backend/bootstrap_backend.js'; export default async function ({provide}) { const bootstrapped = await bootstrap_backend(deno_backend_config()); provide('backend_handle', bootstrapped); return async () => { await bootstrapped.teardown(); }; }

    If bootstrap() throws — typically a bad token, port collision, or keeper-username mismatch — the spawned binary is torn down before the error propagates so vitest doesn't strand the port.

  • testing/cross_backend/build_test_backend_paths.ts

    Per-backend filesystem layout under os.tmpdir() for cross-process tests.

    Isolation matters because vitest projects can run in parallel — a shared root would mix daemon tokens across concurrently-running backends. Each backend gets its own subtree via the prefix arg (typically the BackendConfig.name).

    Consumers compose: take the generic paths from build_test_backend_paths(name), add domain-specific dirs (e.g. zzz_dir, scoped_dir) under the returned root.

  • testing/cross_backend/capabilities.ts

    Capability vocabulary for cross-backend integration testing.

    Backends declare which optional behaviors they support; suite bodies call test_if(capabilities.X, ...) to skip cases the backend doesn't implement. No if (config.name === 'rust') branches anywhere — name- checking is a code smell that says capability vocabulary is missing.

    In-process Hono via default_in_process_setup declares every capability true (see in_process_capabilities). Cross-process backends opt in per-flag on their BackendConfig.

  • testing/cross_backend/default_backend_configs.ts

    Family-shared BackendConfig builders for cross-process test backends.

    Two consumer-facing factories — {@link make_default_ts_backend_config} and {@link make_default_rust_backend_config} — own the common shape for the JS-runtime (Deno/Node on V8) and Rust families respectively. Per-backend factories in consumer projects compose a small declaration against one of these and add consumer-specific env vars via extra_env.

    Defaults baked in by family:

    - TS — 'memory://' PGlite, 30s startup window, capabilities without trusted_proxy/login_rate_limit. The TS canonical path leaves these limiters null in test mode. - Rust — caller-supplied real Postgres URL (PGlite isn't reachable from tokio-postgres), 120s startup window (cargo first-build cost), capabilities including trusted_proxy + login_rate_limit + the FUZ_TESTING_RESET_DB_ON_STARTUP=true self-wipe gate.

    Both builders default port_env_var to 'PORT'. Consumers whose binary reads a different name (e.g. zzz's ZZZ_PORT) override.

    Common across both families: /api/rpc, /api/ws, /health, /api/account/bootstrap, cookie_name: 'fuz_session', the standard bootstrap block keyed off default_test_* constants. Builders call build_test_backend_paths(name) internally when the optional paths is omitted.

  • testing/cross_backend/default_secrets.ts

    Default test secrets for cross-process backend bootstrap.

    Every cross-backend consumer needs the same shape: a bootstrap token the harness writes to disk before spawn, a keeper username/password the harness POSTs to /api/account/bootstrap, and cookie keys the binary uses to construct its Keyring. The literals here are dev-only and protected by assert_dev_env; the binaries themselves throw on production load.

    Each constant is exported individually so consumers can override one without re-deriving the rest. Builders in default_backend_configs.ts thread these defaults into the BackendConfig.bootstrap block and the SECRET_FUZ_COOKIE_KEYS env entry; callers compose the bootstrap_overrides knob when they need a non-default keeper.

  • 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.

  • testing/cross_backend/spawn_backend.ts

    Spawn a test backend binary, wait for it to come up, and return a handle the test harness drives.

    Lifecycle:

    1. Write the bootstrap token (config.bootstrap.token) to config.bootstrap.token_path so the binary picks it up at startup. 2. child_process.spawn(...) the binary with detached: true — creates a new process group so a SIGTERM to the negative PID tears down any descendants the binary spawned (PTYs, child workers). vitest worker death + Ctrl+C handlers also fire the group teardown so ports never strand. 3. Poll {base_url}{health_path} until it returns 2xx or startup_timeout_ms elapses. 4. Read config.bootstrap.daemon_token_path to load the binary's deterministic daemon token; thread it onto BackendHandle so _testing_reset and other keeper-credential calls can authenticate.

    Bootstrapping (POST /api/account/bootstrap) is a separate concern — the caller composes bootstrap() from ../transports/bootstrap.ts against a FetchTransport built around handle.config.base_url. Splitting the two keeps spawn_backend consumer-agnostic — fuz_app knows nothing about specific binary contents.

  • testing/cross_backend/standard.ts

    Cross-process counterpart to describe_standard_tests.

    Wires the cross-process-safe subset of the standard bundle — the five suites whose option shape is {setup_test, surface_source, capabilities, ...} and whose bodies fire requests through fixture.transport rather than touching the in-process Backend. Consumers wire one call against a spawned binary instead of repeating the five sibling calls per file.

    Suites included — always run:

    - describe_standard_integration_tests - describe_round_trip_validation - describe_rpc_round_trip_tests - describe_data_exposure_tests

    Gated on roles — included when the consumer supplies a RoleSchemaResult:

    - describe_standard_admin_integration_tests

    Suites omitted — the three that don't survive a process boundary, documented here so per-consumer files don't have to repeat the bookkeeping:

    - describe_rate_limiting_tests — builds a fresh TestApp per test to inject tight per-test rate-limiter overrides. That path requires in-process construction of Backend + rate limiter; the spawned binary has neither knob nor restart-per-test budget. - describe_audit_completeness_tests — reaches into FK-structural introspection that only the in-process backend exposes. Wire-level audit observability lives in the consumer's own audit .cross.test.ts driving audit_log_list / audit_log_role_grant_history. - describe_bootstrap_success_tests — bootstrap is one-shot per backend lifecycle, and the consumer's globalSetup already consumed it before the suite file loads. Re-running would 409.

  • testing/cross_backend/testing_reset_actions.ts

    Test-binary RPC actions for cross-process integration tests.

    Single daemon-token-authed action: _testing_reset — full DB wipe + keeper re-seed + optional secondary-account seeding. The handler wipes every auth-namespace row (no keeper-preserve filter), flips bootstrap_lock back to its post-bootstrap shape, seeds a fresh keeper account inline (reusing create_test_account_with_credentials so cross-process matches in-process write semantics), seeds any caller-requested extra_accounts (also direct-inserted at this setup step), refreshes the daemon-token cache to point at the new keeper, and fires the consumer-supplied domain-state callback. The new keeper + secondary credentials return as the action output so the per-test fixture closes over them.

    The redesign converges in-process and cross-process keeper lifetimes: both modes now run against a freshly bootstrapped keeper per test. Mutation-cascade tests (password change, revoke-all, hardcoded-username signup uniqueness) and direct keeper-vs-admin probes work uniformly cross-process.

    Keeper ≠ admin. The keeper and admin roles are independent. Keeper authorizes daemon-token / bootstrap paths; admin authorizes the user-facing admin RPC surface. _testing_reset seeds the keeper account with [ROLE_KEEPER, ROLE_ADMIN] by default — matching the production bootstrap_account flow — plus any roles passed via extra_keeper_roles. Tests probing the keeper-vs-admin separation (a keeper-only account must 403 on admin RPCs) declare a secondary via extra_accounts: [{username, roles: [ROLE_KEEPER]}] so the account is seeded at this same bootstrap-equivalent step.

    No free-form runtime bypass. Earlier drafts considered a separate _testing_seed_role_grant action for arbitrary direct grants; that was rejected because a runtime bypass would let tests skip the production consent flow's side-effects (audit emit, WS fan-out) and silently mask bugs in those paths. The bypass that does exist — extra_accounts — is framed as bootstrap-time seeding, the same shape bootstrap_account itself uses to grant the initial KEEPER + ADMIN pair. Tests that want a role on a *post-bootstrap* account must route through role_grant_offer_create + role_grant_offer_accept (the production path); they observe the full event chain.

    Production safety: this module lives under cross_backend/ and starts with import '../assert_dev_env.js'; — production bundles either tree-shake the module out or throw at startup. The Rust mirror (fuz_testing crate) ships a parallel action; `cargo xtask check-release blocks fuz_testing` from entering production dep graphs.