import '../assert_dev_env.js';
import { Uuid } from '@fuzdev/fuz_util/id.js';
import type { RouteSpec } from '../../http/route_spec.js';
import type { AppServerContext, BootstrapServerOptions } from '../../server/app_server.js';
import type { SessionOptions } from '../../auth/session_cookie.js';
import { type CreateTestAppOptions, type SuiteAppOptions, type TestAccount } from '../app_server.js';
import { type RpcEndpointsSuiteOption } from '../rpc_helpers.js';
import { type BackendCapabilities } from './capabilities.js';
import type { AppSurfaceSpec } from '../../http/surface.js';
import { type FetchTransport } from '../transports/fetch_transport.js';
import type { SchemaSnapshot } from '../schema_introspect.js';
import type { BackendHandle } from './spawn_backend.js';
/**
 * 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.
 */
export interface CreateTestAccountOptions {
    readonly username?: string;
    readonly password_value?: string;
    readonly roles?: Array<string>;
}
/**
 * Shape returned by `TestFixture.create_account`. Aliased to the
 * existing `TestAccount` interface from `app_server.ts` — same shape,
 * stable name on the cross-backend testing surface so call sites read
 * `fixture.create_account(...)` returning `TestAccountFixture` without
 * crossing module boundaries.
 */
export type TestAccountFixture = TestAccount;
/**
 * 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.
 */
export interface ExtraAccountSpec {
    readonly username: string;
    readonly password_value?: string;
    readonly roles: ReadonlyArray<string>;
}
/** Bootstrap-time-seeded secondary account exposed on the fixture. */
export interface ExtraAccountFixture {
    readonly account: {
        readonly id: Uuid;
        readonly username: string;
    };
    readonly actor: {
        readonly id: Uuid;
    };
    readonly api_token: string;
    readonly session_cookie: string;
    readonly create_session_headers: (extra?: Record<string, string>) => Record<string, string>;
    readonly create_bearer_headers: (extra?: Record<string, string>) => Record<string, string>;
}
/**
 * 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]`.
 */
export interface TestFixtureBase {
    /**
     * 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 transport: FetchTransport;
    /**
     * 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`.
     */
    readonly fresh_transport: (options?: {
        readonly origin?: string | null;
    }) => FetchTransport;
    /** The freshly-bootstrapped keeper account. */
    readonly account: {
        readonly id: Uuid;
        readonly username: string;
    };
    /** The actor linked to the keeper account. */
    readonly actor: {
        readonly id: Uuid;
    };
    /** Build request headers with the keeper's session cookie. */
    readonly create_session_headers: (extra?: Record<string, string>) => Record<string, string>;
    /** Build request headers with the keeper's bearer token. */
    readonly create_bearer_headers: (extra?: Record<string, string>) => Record<string, string>;
    /** Build request headers with the daemon token (keeper auth). */
    readonly create_daemon_token_headers: (extra?: Record<string, string>) => Record<string, string>;
    /**
     * 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.
     */
    readonly create_account: (options?: CreateTestAccountOptions) => Promise<TestAccountFixture>;
    /**
     * 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.
     */
    readonly extra_accounts: Readonly<Record<string, ExtraAccountFixture>>;
    /**
     * Forge an *expired server-side session* for the keeper account and
     * return the ready-to-send `Cookie` header value (`name=value`). The
     * minted `auth_session` row is backdated while the signed cookie payload
     * stays valid — so resolution clears the cookie-payload gate
     * (`parse_session`) and is refused at the authoritative DB-row gate
     * (`query_session_get_valid` — `WHERE expires_at > NOW()`). Backs the
     * `expired_session` conformance principal. In-process mints directly via
     * `mint_test_session`; cross-process drives the `_testing_mint_session`
     * RPC over the keeper's daemon-token channel (the driver has no keyring).
     */
    readonly mint_expired_session: () => Promise<string>;
}
/**
 * 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.
 *
 * Transport-agnostic: in-process and cross-process producers return the
 * same shape. Behaviors that once needed raw backend access (keyring for
 * forging cookies) are reached through wire-shaped seams instead —
 * `mint_expired_session()` mints over the `_testing_mint_session` channel
 * cross-process and directly in-process, so suite bodies never branch on
 * the transport.
 */
export type TestFixture = TestFixtureBase;
/**
 * 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.
 */
export type SetupTest = () => Promise<TestFixture>;
/**
 * 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.
 */
export interface InProcessSetupOptions extends CreateTestAppOptions {
    /**
     * 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.
     */
    readonly extra_accounts?: ReadonlyArray<ExtraAccountSpec>;
}
/**
 * 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.
 */
export declare const default_in_process_setup: (options: InProcessSetupOptions) => SetupTest;
/**
 * Cross-process backend handle enriched with the bootstrapped keeper's
 * captured credentials. Consumers compose this in vitest's
 * `globalSetup`:
 *
 * ```ts
 * 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.
 */
export interface BootstrappedBackendHandle extends BackendHandle {
    /** Transport carrying the keeper session cookie + cookie jar. */
    readonly keeper_transport: FetchTransport;
    /** Keeper account JSON captured from `POST /bootstrap`. */
    readonly keeper_account: {
        readonly id: Uuid;
        readonly username: string;
    };
    /** Keeper actor JSON captured from `POST /bootstrap`. */
    readonly keeper_actor: {
        readonly id: Uuid;
    };
    /** Raw keeper `Set-Cookie` values — thread into `ws_transport` for keeper-authenticated WS upgrades. */
    readonly keeper_cookies: ReadonlyArray<string>;
}
/**
 * `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`.
 */
export type ReconstructedBootstrappedBackendHandle = Omit<BootstrappedBackendHandle, 'child' | 'teardown'>;
/**
 * 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`).
 */
export interface SerializableBootstrappedBackendHandle {
    readonly config: BackendHandle['config'];
    readonly daemon_token: BackendHandle['daemon_token'];
    readonly keeper_account: BootstrappedBackendHandle['keeper_account'];
    readonly keeper_actor: BootstrappedBackendHandle['keeper_actor'];
    readonly keeper_cookies: ReadonlyArray<string>;
}
/**
 * Strip the non-serializable members so the result can be passed to
 * vitest's `project.provide`. Call in `globalSetup` before provide.
 */
export declare const serialize_bootstrapped_handle: (handle: BootstrappedBackendHandle) => SerializableBootstrappedBackendHandle;
/**
 * 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.
 */
export declare const reconstruct_bootstrapped_handle: (serialized: SerializableBootstrappedBackendHandle) => ReconstructedBootstrappedBackendHandle;
/** Options for `default_cross_process_setup`. */
export interface CrossProcessSetupOptions {
    /**
     * 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.
     */
    readonly extra_keeper_roles?: ReadonlyArray<string>;
    /**
     * 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.
     */
    readonly extra_accounts?: ReadonlyArray<ExtraAccountSpec>;
}
/**
 * Capture a backend's schema snapshot over the `_testing_schema_snapshot`
 * RPC action (keeper daemon-token channel). The canonical way for a
 * cross-impl parity gate to read each backend's live schema — pair two
 * calls with `assert_schema_snapshots_equal` (`testing/schema_parity.js`).
 *
 * `exclude_tables` drops documented divergences from both sides before
 * comparison (e.g. a cell-primary Rust backend lacks tables the TS schema
 * has). Each impl answers from its own introspection — TS via
 * `query_schema_snapshot`, Rust via `fuz_db::query_schema_snapshot` — and
 * the snapshot shapes match by design.
 */
export declare const capture_schema_snapshot: (handle: ReconstructedBootstrappedBackendHandle, options?: {
    exclude_tables?: ReadonlyArray<string>;
}) => Promise<SchemaSnapshot>;
/**
 * 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.
 */
export declare const default_cross_process_setup: (handle: ReconstructedBootstrappedBackendHandle, options?: CrossProcessSetupOptions) => SetupTest;
/**
 * 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.
 */
export interface DefaultInProcessSuiteOptions {
    session_options: SessionOptions<string>;
    create_route_specs: (ctx: AppServerContext) => Array<RouteSpec>;
    rpc_endpoints?: RpcEndpointsSuiteOption;
    /**
     * 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).
     */
    bootstrap?: BootstrapServerOptions;
    app_options?: SuiteAppOptions;
    /**
     * 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.
     */
    extra_keeper_roles?: Array<string>;
    /**
     * 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.
     */
    extra_accounts?: ReadonlyArray<ExtraAccountSpec>;
    /**
     * 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`).
     */
    surface_source?: AppSurfaceSpec;
}
/**
 * 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`, ...).
 *
 * ```ts
 * // 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.
 */
export declare const default_in_process_suite_options: <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"];
};
//# sourceMappingURL=setup.d.ts.map